Implied Volatility and Greeks of Index 'At The Money' Options: Part 1¶

In part 1: we will (i) automatically find the Option (of choice) closest to At The Money (ATM) and (ii) calculate its Implied Volatility & Greeks. We focus below on Future (Monthly) Options on the Index '.STOXX50E' (EURO STOXX 50 EUR PRICE INDEX) ('EUREX') and '.SPX' (S&P 500 INDEX), although you can apply the logic below for another index. To find the ATM instrument, we simply and efficiently use the Search API. Usually, the calculation of the Black-Scholes-Merton model's Implied Volatility involves numerical techniques, since it is not a closed equation (unless restricting assumptions that log returns follow a standard normal distribution with mean is zero, $\mu$ = 0, and standard deviation is zero, $\sigma$ = 1, are made). If we used these techniques in calculating each Implied Volatility value on our computer, it would take several seconds - if not minutes - for each data point computed. I have chosen to use the Instrument Pricing Analytics (IPA) service in the Python Refinitiv Data Library instead, as this service allows me to send model specifications (and variables) and receive several (up to 100) computed Implied Volatility values in one go - in a few seconds. Not only does this save a great deal of time, but also many lines of code! Fianlly, we will put it all in one function.

In part 2: We will implement a functionality allowing us to apply all we did in Part 1 to expired options. You'll see, it's not as simple as it seems. We will then put it all in one function using Type Hints. This, in itself, will also be rather new and exciting!

In [11]:
import refinitiv.data as rd  # This is LSEG's Data and Analytics' API wrapper, called the Refinitiv Data Library for Python. You can update this library with the comand `!pip install refinitiv-data --upgrade`
from refinitiv.data.content import historical_pricing  # We will use this Python Class in `rd` to show the Implied Volatility data already available before our work.
from refinitiv.data.content import search  # We will use this Python Class in `rd` to fid the instrument we are after, closest to At The Money.
import refinitiv.data.content.ipa.financial_contracts as rdf  # We're going to need this to use the content layer of the RD library and the calculators of greeks and Impl Volat in Instrument Pricing Analytics (IPA) and Exchange Traded Instruments (ETI)
from refinitiv.data.content.ipa.financial_contracts import option  # We're going to need this to use the content layer of the RD library and the calculators of greeks and Impl Volat in IPA & ETI

import numpy as np  # We need `numpy` for mathematical and array manipilations.
import pandas as pd  # We need `pandas` for datafame and array manipilations.
import calendar  # We use `calendar` to identify holidays and maturity dates of intruments of interest.
import pytz  # We use `pytz` to manipulate time values aiding `calendar` library. to import its types, you might need to run `!python3 -m pip install types-pytz`
import pandas_market_calendars as mcal  # Used to identify holidays. See `https://github.com/rsheftel/pandas_market_calendars/blob/master/examples/usage.ipynb` for info on this market calendar library
from datetime import datetime, timedelta, timezone  # We use these to manipulate time values
from dateutil.relativedelta import relativedelta  # We use `relativedelta` to manipulate time values aiding `calendar` library.
import requests  # We'll need this to send requests to servers vie a the delivery layer - more on that below

# `plotly` is a library used to render interactive graphs:
import plotly.graph_objects as go
import plotly.express as px  # This is just to see the implied vol graph when that field is available
import matplotlib.pyplot as plt  # We use `matplotlib` to just in case users do not have an environment suited to `plotly`.
from IPython.display import clear_output, display  # We use `clear_output` for users who wish to loop graph production on a regular basis. We'll use this to `display` data (e.g.: pandas data-frames).
from plotly import subplots
import plotly

Now let's open our session with RD. You can find more information about sessions on EX-4.01.01-Sessions.ipynb.

In [12]:
try:
    rd.open_session(
        name="desktop.workspace",
        config_name="C:/Example.DataLibrary.Python-main/Configuration/refinitiv-data.config.json")
except:
    rd.open_session()
# For more info on the session, use `rd.get_config().as_dict()`
In [13]:
test_df = rd.get_data(
    universe=['DSMF4'],
    fields=["PROV_SYMB"],
    # interval="tick"
    )
In [14]:
test_df
Out[14]:
Instrument PROV_SYMB
0 DSMF4 m2401
In [15]:
rd.get_data(universe=["VOD.L"], fields=["TR.RIC"])  # EMAH8^A
Out[15]:
Instrument RIC
0 VOD.L VOD.L
In [16]:
for i, j in zip(
    ['refinitiv.data', 'numpy', 'pandas', 'pandas_market_calendars' 'pytz', 'requests', 'plotly'],
    [rd, np, pd, mcal, pytz, requests, plotly]):
    print(f"{i} used in this code is version {j.__version__}")
refinitiv.data used in this code is version 1.2.0
numpy used in this code is version 1.21.2
pandas used in this code is version 1.3.5
pandas_market_calendarspytz used in this code is version 4.1.0
requests used in this code is version 2019.3
plotly used in this code is version 2.27.1

FYI (For Your Information), this is out Python version:

In [17]:
!python -V
Python 3.8.2

EUREX Call Options¶

In this article, we will attempt to calculate the Implied Volatility (IV) for Future Options on 2 indexes (.STOXX50E & .SPX) trading 'ATM', meaning that the contract's strike price is at (or near - within x%) parity with (equal to) its current treading price (TRDPRC_1). We are also only looking for such Options expiring within a set time window; allowing for the option 'forever', i.e.: that expire whenever after date of calculation. To do so, we 1st have to find the option in question. To find live Options, we best use the Search API. To find Expired Options we will use functions created in Haykaz's amazing articles "Finding Expired Options and Backtesting a Short Iron Condor Strategy" & "Functions to find Option RICs traded on different exchanges"

Finding Live Options (using Search API)¶

Live Options, in this context, are Options that have not expired at time of computation. To be explicit:

  • 'time of calculation' refers here to the time for which the calculation is done, i.e.: if we compute today an IV for an Option as if it was 3 days ago, 'time of calculation' is 3 days ago.
  • 'time of computation' refers here to the time when we are computing the values, i.e.: if we compute today an IV for an Option as if it was 3 days ago, 'time of computation' is today.

As aforementioned, to find live Options, we best use the Search API: Here we look for options on .STOXX50E that mature on the 3rd friday of December 2023:

In [18]:
response1 = search.Definition(
    view=search.Views.SEARCH_ALL,  # To see what views are available: `help(search.Views)` & `search.metadata.Definition(view = search.Views.SEARCH_ALL).get_data().data.df.to_excel("SEARCH_ALL.xlsx")`
    query=".STOXX50E",
    select="DocumentTitle, RIC, StrikePrice, ExchangeCode, ExpiryDate, UnderlyingAsset, " +
            "UnderlyingAssetName, UnderlyingAssetRIC, ESMAUnderlyingIndexCode, RCSUnderlyingMarket" +
            "UnderlyingQuoteName, UnderlyingQuoteRIC, InsertDateTime, RetireDate",
    filter="RCSAssetCategoryLeaf eq 'Option' and RIC eq 'STX*' and DocumentTitle ne '*Weekly*' " +
    "and CallPutOption eq 'Call' and ExchangeCode eq 'EUX' and " +
    "ExpiryDate ge 2023-11-29 and ExpiryDate lt 2023-12-31",  # ge (greater than or equal to), gt (greater than), lt (less than) and le (less than or equal to). These can only be applied to numeric and date properties.
    top=100).get_data()
search_df1 = response1.data.df
search_df1
Out[18]:
DocumentTitle RIC StrikePrice ExchangeCode ExpiryDate UnderlyingQuoteRIC InsertDateTime RetireDate
0 Eurex EURO STOXX 50 Monthly Index Option 4300 ... STXE43000L3.EX 4300 EUX 2023-12-15 [.STOXX50E] 2023-03-09 05:27:25 2023-12-19
1 Eurex EURO STOXX 50 Monthly Index Option 4500 ... STXE45000L3.EX 4500 EUX 2023-12-15 [.STOXX50E] 2023-03-09 05:15:11 2023-12-19
2 Eurex EURO STOXX 50 Monthly Index Option 4250 ... STXE42500L3.EX 4250 EUX 2023-12-15 [.STOXX50E] 2023-03-09 04:07:50 2023-12-19
3 Eurex EURO STOXX 50 Monthly Index Option 4200 ... STXE42000L3.EX 4200 EUX 2023-12-15 [.STOXX50E] 2023-03-09 05:25:42 2023-12-19
4 Eurex EURO STOXX 50 Monthly Index Option 4100 ... STXE41000L3.EX 4100 EUX 2023-12-15 [.STOXX50E] 2023-03-09 05:21:27 2023-12-19
... ... ... ... ... ... ... ... ...
95 Eurex EURO STOXX 50 Monthly Index Option 5050 ... STXE50500L3.EX 5050 EUX 2023-12-15 [.STOXX50E] 2023-03-09 03:58:43 2023-12-19
96 Eurex EURO STOXX 50 Monthly Index Option 3775 ... STXE37750L3.EX 3775 EUX 2023-12-15 [.STOXX50E] 2023-03-09 03:49:44 2023-12-19
97 Eurex EURO STOXX 50 Monthly Index Option 1900 ... STXE19000L3.EX 1900 EUX 2023-12-15 [.STOXX50E] 2023-03-09 06:15:22 2023-12-19
98 Eurex EURO STOXX 50 Monthly Index Option 2800 ... STXE28000L3.EX 2800 EUX 2023-12-15 [.STOXX50E] 2023-03-09 05:16:35 2023-12-19
99 Eurex EURO STOXX 50 Monthly Index Option 3825 ... STXE38250L3.EX 3825 EUX 2023-12-15 [.STOXX50E] 2023-03-09 03:51:31 2023-12-19

100 rows × 8 columns

N.B.: When using the Filter in Search and playing with dates, it is good to read the API Playground Documentation; it mentions that: "Dates are written in ISO datetime format. The time portion is optional, as is the timezone (assumed to be UTC unless otherwise specified). Valid examples include 2012-03-11T17\:13:55Z, 2012-03-11T17\:13:55, 2012-03-11T12\:00-03:30, 2012-03-11.":

Let's find the current underlying price, then we can pick the option with strike price closest to that, i.e.: the most 'At The Money'; note that this means that the option can be in or out the money, as long as it is the closest to at the money:

In [19]:
current_underlying_prc = rd.get_history(
    universe=[search_df1.UnderlyingQuoteRIC[0][0]],
    fields=["TRDPRC_1"],
    interval="tick").iloc[-1][0]
current_underlying_prc
Out[19]:
4149.72
In [20]:
optn = search_df1.iloc[(search_df1['StrikePrice']-current_underlying_prc).abs().argsort()[:1]]
optn
Out[20]:
DocumentTitle RIC StrikePrice ExchangeCode ExpiryDate UnderlyingQuoteRIC InsertDateTime RetireDate
18 Eurex EURO STOXX 50 Monthly Index Option 4150 ... STXE41500L3.EX 4150 EUX 2023-12-15 [.STOXX50E] 2023-03-09 04:11:37 2023-12-19
In [21]:
optn_strk_pr = optn['StrikePrice'].to_list()[0]
opn_RIC = optn['RIC'].to_list()[0]
opn_exp_date_str = str(optn.ExpiryDate.to_list()[0])[:10]

if optn_strk_pr > current_underlying_prc:
    optn_explainer_a = str(
        f"higher than the spot price of our underlying which is {current_underlying_prc}")
    optn_explainer_b = str(
        f"a loss of ({optn_strk_pr} - {current_underlying_prc} =) {optn_strk_pr - current_underlying_prc}")
    moneyness = "'Out-The-Money'"
if optn_strk_pr == current_underlying_prc:
    optn_explainer_a = str(
        f"equal to the spot price of our underlying which is {current_underlying_prc}")
    optn_explainer_b = str(
        f"neither a gain or a loss [of ({current_underlying_prc} - {optn_strk_pr} =) {current_underlying_prc - optn_strk_pr}]")
    moneyness = "exactly 'At-The-Money'"
if optn_strk_pr < current_underlying_prc:
    optn_explainer_a = str(
        f"lower than the spot price of our underlying which is {current_underlying_prc}")
    optn_explainer_b = str(
        f"a gain of ({current_underlying_prc} - {optn_strk_pr} =) {current_underlying_prc - optn_strk_pr}")
    moneyness = "'In-The-Money'"

print(f"In this instance, for this Call Option, '{opn_RIC}', " +
      f"the strike price is {optn_strk_pr},")
print(f"{optn_explainer_a}.")
print(f"The holder of this '{opn_RIC}' option has the right " +
      f"(but not the obligation) to buy the underlying for {optn_strk_pr}, ")
print(f"which, was the price of the underlying to stay the same till expiry (" +
      f"{current_underlying_prc} on {opn_exp_date_str}),")
print(f"means {optn_explainer_b}.")
print(f"This option, in this instance, is {moneyness}.")
In this instance, for this Call Option, 'STXE41500L3.EX', the strike price is 4150,
higher than the spot price of our underlying which is 4149.72.
The holder of this 'STXE41500L3.EX' option has the right (but not the obligation) to buy the underlying for 4150, 
which, was the price of the underlying to stay the same till expiry (4149.72 on 2023-12-15),
means a loss of (4150 - 4149.72 =) 0.27999999999974534.
This option, in this instance, is 'Out-The-Money'.

Function for Expiration Days¶

Most of the time, market agents will be interested in the next expiring Option, unless we are too close to it. We would not be interested, for example, in an option expiring in 1 hour, or even tomorrow, because that is so close (in time) that the information reflected in the Option's trades in the market does not represent future expectations of its underlying, but current expectations of it.

To implement such a logic, we need to know what are the expiry dates of the option that we are interested in. We are looking for a Python function narrowing our search to options expiring on the 3rd Friday of any one month. For info on this function, please read articles "Finding Expired Options and Backtesting a Short Iron Condor Strategy" & "Functions to find Option RICs traded on different exchanges"

In [22]:
def Get_exp_dates(year, days=True, mcal_get_calendar='EUREX'):
    '''
    Get_exp_dates Version 2.0:

    This function gets expiration dates for a year for NDX options, which are the 3rd Fridays of each month.

    Changes
    ----------------------------------------------
    Changed from Version 1.0 to 2.0: Jonathan Legrand changed Haykaz Aramyan's original code to allow
        (i) function name changed from `get_exp_dates` to `Get_exp_dates`
        (ii) for the function's holiday argument to be changed, allowing for any calendar supported by `mcal.get_calendar` and defaulted to 'EUREX' as opposed to 'CBOE_Index_Options' and
        (iii) for the function to output full date objects as opposed to just days of the month if agument days=True.

    Dependencies
    ----------------------------------------------
    Python library 'pandas_market_calendars' version '3.2'.
    pandas_market_calendars as mcal version '4.1.0'.

    Parameters
    -----------------------------------------------
    Input:
        year(int): year for which expiration days are requested

        mcal_get_calendar(str): String of the calendar for which holidays have to be taken into account. More on this calendar (link to Github checked 2022-10-11): https://github.com/rsheftel/pandas_market_calendars/blob/177e7922c7df5ad249b0d066b5c9e730a3ee8596/pandas_market_calendars/exchange_calendar_cboe.py
            Default: mcal_get_calendar='EUREX'

        days(bool): If True, only days of the month is outputed, else it's dataeime objects
            Default: days=True

    Output:
        dates(dict): dictionary of expiration days for each month of a specified year in datetime.date format.
    '''

    # get CBOE market holidays
    EUREXCal = mcal.get_calendar(mcal_get_calendar)
    holidays = EUREXCal.holidays().holidays

    # set calendar starting from Saturday
    c = calendar.Calendar(firstweekday=calendar.SATURDAY)

    # get the 3rd Friday of each month
    exp_dates = {}
    for i in range(1, 13):
        monthcal = c.monthdatescalendar(year, i)
        date = monthcal[2][-1]
        # check if found date is an holiday and get the previous date if it is
        if date in holidays:
            date = date + timedelta(-1)
        # append the date to the dictionary
        if year in exp_dates:
            ### Changed from original code from here on by Jonathan Legrand on 2022-10-11
            if days: exp_dates[year].append(date.day)
            else: exp_dates[year].append(date)
        else:
            if days: exp_dates[year] = [date.day]
            else: exp_dates[year] = [date]
    return exp_dates
In [23]:
full_dates = Get_exp_dates(2022, days=False)
dates = Get_exp_dates(2022)
full_dates_str_dict = {i: [full_dates[i][j].strftime('%Y-%m-%d')
                        for j in range(len(full_dates[i]))]
                        for i in list(full_dates.keys())}
full_dates_day_dict = {i: [full_dates[i][j].day
                        for j in range(len(full_dates[i]))]
                        for i in list(full_dates.keys())}
In [24]:
print(f"full_dates: {full_dates} \n")  # `\n` prints a new line
print(f"full_dates_str_dict: {full_dates_str_dict} \n")
print(f"dates: {dates} \n")
print(f"full_dates_day_dict: {full_dates_day_dict}")
full_dates: {2022: [datetime.date(2022, 1, 21), datetime.date(2022, 2, 18), datetime.date(2022, 3, 18), datetime.date(2022, 4, 14), datetime.date(2022, 5, 20), datetime.date(2022, 6, 17), datetime.date(2022, 7, 15), datetime.date(2022, 8, 19), datetime.date(2022, 9, 16), datetime.date(2022, 10, 21), datetime.date(2022, 11, 18), datetime.date(2022, 12, 16)]} 

full_dates_str_dict: {2022: ['2022-01-21', '2022-02-18', '2022-03-18', '2022-04-14', '2022-05-20', '2022-06-17', '2022-07-15', '2022-08-19', '2022-09-16', '2022-10-21', '2022-11-18', '2022-12-16']} 

dates: {2022: [21, 18, 18, 14, 20, 17, 15, 19, 16, 21, 18, 16]} 

full_dates_day_dict: {2022: [21, 18, 18, 14, 20, 17, 15, 19, 16, 21, 18, 16]}

Function to find the next expiring Option outside the next x day window¶

E.g.: I would like to know what is the next Future (Monthly) Option (i) on the Index '.STOXX50E' (ii) closest to ATM (i.e.: with an underlying spot price closest to the option's strike price) and (iii) Expiring in more than x days (i.e.: not too close to calculated time 't'), let's say 15 days:

In [25]:
x = 15
In [26]:
time_of_calc_datetime = datetime.now()  # For now, we will focuss on the use-case where we are calculating values for today; later we will allow for it historically for any day going back a few business days.
time_of_calc_str = datetime.now().strftime('%Y-%m-%d')
time_of_calc_str
Out[26]:
'2023-09-27'
In [27]:
full_dates_at_time_of_calc = Get_exp_dates(
    time_of_calc_datetime.year, days=False)  # `time_of_calc_datetime.year` here is 2023
full_dates_at_time_of_calc_datetime = [
    datetime(i.year, i.month, i.day)
    for i in full_dates_at_time_of_calc[
        list(full_dates_at_time_of_calc.keys())[0]]]
print(full_dates_at_time_of_calc_datetime)
[datetime.datetime(2023, 1, 20, 0, 0), datetime.datetime(2023, 2, 17, 0, 0), datetime.datetime(2023, 3, 17, 0, 0), datetime.datetime(2023, 4, 21, 0, 0), datetime.datetime(2023, 5, 19, 0, 0), datetime.datetime(2023, 6, 16, 0, 0), datetime.datetime(2023, 7, 21, 0, 0), datetime.datetime(2023, 8, 18, 0, 0), datetime.datetime(2023, 9, 15, 0, 0), datetime.datetime(2023, 10, 20, 0, 0), datetime.datetime(2023, 11, 17, 0, 0), datetime.datetime(2023, 12, 15, 0, 0)]
In [28]:
expiry_date_of_int = [i for i in full_dates_at_time_of_calc_datetime
                   if i > time_of_calc_datetime + relativedelta(days=x)][0]
expiry_date_of_int
Out[28]:
datetime.datetime(2023, 10, 20, 0, 0)

Now we can look for the one option we're after:

In [29]:
response2 = search.Definition(
    view=search.Views.SEARCH_ALL, # To see what views are available: `help(search.Views)` & `search.metadata.Definition(view = search.Views.SEARCH_ALL).get_data().data.df.to_excel("SEARCH_ALL.xlsx")`
    query=".STOXX50E",
    select="DocumentTitle, RIC, StrikePrice, ExchangeCode, ExpiryDate, UnderlyingAsset, " +
            "UnderlyingAssetName, UnderlyingAssetRIC, ESMAUnderlyingIndexCode, RCSUnderlyingMarket" +
            "UnderlyingQuoteName, UnderlyingQuoteRIC, InsertDateTime, RetireDate",
    filter="RCSAssetCategoryLeaf eq 'Option' and RIC eq 'STX*' and DocumentTitle ne '*Weekly*' " +
    "and CallPutOption eq 'Call' and ExchangeCode eq 'EUX' and " +
    f"ExpiryDate ge {(expiry_date_of_int - relativedelta(days=1)).strftime('%Y-%m-%d')} " +
    f"and ExpiryDate lt {(expiry_date_of_int + relativedelta(days=1)).strftime('%Y-%m-%d')}",  # ge (greater than or equal to), gt (greater than), lt (less than) and le (less than or equal to). These can only be applied to numeric and date properties.
    top=10000,
).get_data()
searchDf2 = response2.data.df
In [30]:
searchDf2
Out[30]:
DocumentTitle RIC StrikePrice ExchangeCode ExpiryDate UnderlyingQuoteRIC InsertDateTime RetireDate
0 Eurex EURO STOXX 50 Monthly Index Option 4300 ... STXE43000J3.EX 4300 EUX 2023-10-20 [.STOXX50E] 2023-03-09 03:49:34 2023-10-24
1 Eurex EURO STOXX 50 Monthly Index Option 4350 ... STXE43500J3.EX 4350 EUX 2023-10-20 [.STOXX50E] 2023-03-09 03:41:50 2023-10-24
2 Eurex EURO STOXX 50 Monthly Index Option 4375 ... STXE43750J3.EX 4375 EUX 2023-10-20 [.STOXX50E] 2023-03-09 03:46:01 2023-10-24
3 Eurex EURO STOXX 50 Monthly Index Option 4250 ... STXE42500J3.EX 4250 EUX 2023-10-20 [.STOXX50E] 2023-03-09 03:48:58 2023-10-24
4 Eurex EURO STOXX 50 Monthly Index Option 4400 ... STXE44000J3.EX 4400 EUX 2023-10-20 [.STOXX50E] 2023-03-09 03:44:57 2023-10-24
... ... ... ... ... ... ... ... ...
151 Eurex EURO STOXX 50 Monthly Index Option 1750 ... STXE17500J3.EX 1750 EUX 2023-10-20 [.STOXX50E] 2023-03-09 03:48:57 2023-10-24
152 Eurex EURO STOXX 50 Monthly Index Option 3625 ... STXE36250J3.EX 3625 EUX 2023-10-20 [.STOXX50E] 2023-03-09 03:54:09 2023-10-24
153 Eurex EURO STOXX 50 Monthly Index Option 2875 ... STXE28750J3.EX 2875 EUX 2023-10-20 [.STOXX50E] 2023-03-09 03:44:41 2023-10-24
154 Eurex EURO STOXX 50 Monthly Index Option 3025 ... STXE30250J3.EX 3025 EUX 2023-10-20 [.STOXX50E] 2023-03-09 03:52:01 2023-10-24
155 Eurex EURO STOXX 50 Monthly Index Option 1000 ... STXE10000J3.EX 1000 EUX 2023-10-20 [.STOXX50E] 2023-03-09 03:41:17 2023-10-24

156 rows × 8 columns

And again, we can collect the closest to ATM:

In [31]:
closest_atm_optn = searchDf2.iloc[(searchDf2['StrikePrice']-current_underlying_prc).abs().argsort()[:1]]
closest_atm_optn
Out[31]:
DocumentTitle RIC StrikePrice ExchangeCode ExpiryDate UnderlyingQuoteRIC InsertDateTime RetireDate
14 Eurex EURO STOXX 50 Monthly Index Option 4150 ... STXE41500J3.EX 4150 EUX 2023-10-20 [.STOXX50E] 2023-03-09 03:48:43 2023-10-24

Now we have our instrument:

In [32]:
instrument = closest_atm_optn.RIC.values[0]
instrument
Out[32]:
'STXE41500J3.EX'

Refinitiv-provided Daily Implied Volatility¶

Refinitiv provides pre-calculated Implied Volatility values, but they are daily, and we will look into calculating them in higher frequencies:

In [33]:
## Example Options:

# instrument_1 = 'SPXv212240000.U'
# instrument_2 = 'STXE35500J2.EX'  # Eurex Dow Jones EURO STOXX 50 Index Option 3550 Call Oct 2022, Stock Index Cash Option, Underlying RIC: .STOXX50E
# instrument_3 = 'SPXj212240000.U'
In [34]:
start = (time_of_calc_datetime - pd.tseries.offsets.BDay(5)).strftime('%Y-%m-%dT%H:%M:%S.%f')  # `datetime.now().isoformat(timespec='minutes')`
start
Out[34]:
'2023-09-20T10:27:45.460240'
In [35]:
end_date_time = datetime.now()
end = end_date_time.strftime('%Y-%m-%dT%H:%M:%S.%f')  #  e.g.: '2022-09-09T20:00:00.000'
end
Out[35]:
'2023-09-27T10:27:46.761469'
In [36]:
_ref_daily_imp_vol_df = historical_pricing.events.Definition(
    instrument, fields=['IMP_VOLT'], count=2000).get_data()
_ref_daily_imp_vol_df.data.df.head()

try:
    def_daily_imp_vol_df = _ref_daily_imp_vol_df.data.df.drop(['EVENT_TYPE'], axis=1)  # In codebook, this line is needed
except:
    def_daily_imp_vol_df = _ref_daily_imp_vol_df.data.df # If outside of codebook
fig = px.line(
    def_daily_imp_vol_df,
    title = def_daily_imp_vol_df.columns.name + " " + def_daily_imp_vol_df.columns[0])  # This is just to see the implied vol graph when that field is available
fig.show()

Option Price¶

Now things are getting tricky. Certain Expiered Options do not have TRDPRC_1 data historically. Some don't have SETTLE. Some have both... The below should capture TRDPRC_1 when it is available, but SETTLE might still be present in these instances. So we will need to build a logic to focus on the series with the most datapoints. Specifically: Get TRDPRC_1. If there are fewer TRDPRC_1 datapoionts than days (i.e.: if there's only daily data for this field), get SETTLE. Same again, if not SETTLE, then get the midpoint between BID and ASK.

In [37]:
__optn_mrkt_price_gmt = rd.content.historical_pricing.summaries.Definition(
    universe=instrument,
        start=start,  # Ought to always start at 4 am for OPRA exchanged Options, more info in the article below
        end=end,  # Ought to always end at 8 pm for OPRA exchanged Options, more info in the article below
        interval='PT10M', # `PT1M`
        fields=['TRDPRC_1', 'SETTLE', 'BID', 'ASK']).get_data().data.df # 'LST_TRD_PR', 'CF_LAST', 'CF_CLOSE', 'SETTLE', 'TRDPRC_1', 'BID', 'ASK'
__optn_mrkt_price_gmt_cnt = __optn_mrkt_price_gmt.count()
if __optn_mrkt_price_gmt_cnt.TRDPRC_1 > 0:
    __optn_mrkt_price_gmt = pd.DataFrame(
        data={'TRDPRC_1':
              __optn_mrkt_price_gmt.TRDPRC_1}).dropna()
elif __optn_mrkt_price_gmt_cnt.SETTLE > 0:
    __optn_mrkt_price_gmt = pd.DataFrame(
        data={'SETTLE':
              __optn_mrkt_price_gmt.SETTLE}).dropna()
elif __optn_mrkt_price_gmt_cnt.BID > 0:
    __optn_mrkt_price_gmt = pd.DataFrame(
        data={'MID': (
            __optn_mrkt_price_gmt.BID + __optn_mrkt_price_gmt.ASK)/2}).dropna()
if __optn_mrkt_price_gmt.empty:
    print(f"No data could be found for {instrument}, please check it on Refinitiv Workspace")

_optn_mrkt_price_gmt = __optn_mrkt_price_gmt.copy()
_optn_mrkt_price_gmt.columns = [f"Optn {__optn_mrkt_price_gmt.columns[0]} Pr"]
_optn_mrkt_price_gmt.columns.name = instrument
_optn_mrkt_price_gmt
Out[37]:
STXE41500J3.EX Optn TRDPRC_1 Pr
Timestamp
2023-09-21 10:40:00 119.0
2023-09-21 11:00:00 120.8
2023-09-22 07:10:00 106.8
2023-09-22 07:20:00 113.4
2023-09-22 07:40:00 111.0
2023-09-22 08:00:00 102.3
2023-09-22 08:30:00 109.8
2023-09-25 08:50:00 89.9
2023-09-25 09:30:00 94.0
2023-09-25 12:10:00 86.0
2023-09-25 12:30:00 85.3
2023-09-25 13:10:00 82.1
2023-09-25 13:20:00 83.0
2023-09-25 13:30:00 78.0
2023-09-25 13:50:00 86.1
2023-09-25 14:00:00 86.3
2023-09-25 14:10:00 86.1
2023-09-25 14:20:00 80.6
2023-09-25 14:30:00 80.8
2023-09-25 14:40:00 81.0
2023-09-25 14:50:00 78.2
2023-09-25 15:00:00 79.4
2023-09-25 15:10:00 85.1
2023-09-25 15:20:00 85.9
2023-09-26 07:00:00 71.4
2023-09-26 07:10:00 68.2
2023-09-26 07:20:00 64.5
2023-09-26 07:30:00 69.0
2023-09-26 07:40:00 68.8
2023-09-26 07:50:00 67.8
2023-09-26 08:20:00 74.5
2023-09-26 08:30:00 73.4
2023-09-26 09:40:00 71.9
2023-09-26 10:50:00 68.6
2023-09-26 11:20:00 70.4
2023-09-26 13:20:00 66.3
2023-09-26 13:30:00 69.7
2023-09-26 13:50:00 70.2
2023-09-26 14:00:00 68.8
2023-09-26 14:30:00 69.6
2023-09-26 15:20:00 67.0
2023-09-27 07:10:00 68.6
2023-09-27 07:20:00 69.4
2023-09-27 08:00:00 77.6

Underlying Asset Price¶

Now let's get data for the underying, which we need to calculate IV:

Note also that one may want to only look at 'At Option Trade' datapoints, i.e.: Implied Volatility when a trade is made for the Option, but not when none is made. For this, we will use the 'At Trade' (AT) dataframes:

In [38]:
underlying = closest_atm_optn.UnderlyingQuoteRIC.values[0][0]
underlying
Out[38]:
'.STOXX50E'

Opening Times Of Any One Exchange¶

If you are interested in the opening times of any one exchange, you can use the following:

  1. we can check the META App to get more information about the RIC. If we take STXE43750H3.EX as an example, this RIC has ".EX" which is for the exchange.
  2. Use the page EXCHID01 in Workspace (can be opened via Quote app) to look up the list of exchanges. Click on EXCHID07 as it is the page for exchanges ".EA to .FX".
  3. From EXCHID07, you will see there's 2 .EX exchanges, Germany and Switzerland. Referencing the additional data from the META app, the RIC's country is Germany, so use DE/EXCH10 page
  4. From DE/EXCH10 page, Hours would display the information including the timezone.
  5. From there, you can find the exchange info page with trading hours for Options
In [39]:
# hoursDf = rd.get_data(
#     universe=["EUREX21"],
#     fields=["ROW80_10"])
# display(hoursDf)
# hoursDf.iloc[0, 1]

You may find screens like this:

by searhing 'trading hours' in the Workspace search bar (at the top), then clickin gon Speed Guides. Looking in there, I found the below:

And get the info programatically like this:

In [40]:
hoursDf = rd.get_data(
    universe=["EUREX22"],
    fields=["ROW80_22"]).iloc[0, 1]
hoursDf
Out[40]:
'       OESX/OSTX          07:30-08:50     08:50-17:30     17:30-20:30           '

There isn't nessesarily a trade every 10 min... However, for the statistical inferences that we will make further in the article, when we will calculate Implied Volatilities and therefore implement the Black Scholes model, we will need 'continuous timeseries' with which to deal. There are several ways to go from discrete time series (like ours, even if we go down to tick data), but for this article, we will 1st focus on making 'buckets' of 10 min. If no trade is made in any 10 min. bucket, we will assume the price to have stayed the same as previously, throughout the exchange's trading hours which are:

  • 4am to 8pm ET for OPRA and
  • typically 7:30am to 22:00 CET at the Eurex Exchange (EUREX)

thankfully this is simple. Let's stick with the EUREX for now:

In [41]:
optn_mrkt_price_gmt = _optn_mrkt_price_gmt.resample('10Min').mean()  # get a datapoint every 10 min. Could also use `optn_mrkt_price.append(pd.DataFrame([[pd.NA]], columns=optn_mrkt_price.columns, index=[(end_date_time + (datetime.min - end_date_time) % timedelta(minutes=10))]))` as per https://stackoverflow.com/questions/32723150/rounding-up-to-nearest-30-minutes-in-python
optn_mrkt_price_gmt = optn_mrkt_price_gmt[
    optn_mrkt_price_gmt.index.strftime('%Y-%m-%d').isin(
        [i for i in _optn_mrkt_price_gmt.index.strftime(
            '%Y-%m-%d').unique()])]  # Only keep trading days
In [42]:
# Get timezone of the exchange where option is traded:
optn_mrkt_price = optn_mrkt_price_gmt.copy()
optn_mrkt_price.index = pd.MultiIndex.from_tuples(
    [(i, j) for i, j in zip(
        optn_mrkt_price_gmt.index,
        optn_mrkt_price_gmt.index.tz_localize('GMT').tz_convert('Europe/Berlin'))],
    names=["gmt", "cet"])
optn_mrkt_price
Out[42]:
STXE41500J3.EX Optn TRDPRC_1 Pr
gmt cet
2023-09-21 10:40:00 2023-09-21 12:40:00+02:00 119.0
2023-09-21 10:50:00 2023-09-21 12:50:00+02:00 <NA>
2023-09-21 11:00:00 2023-09-21 13:00:00+02:00 120.8
2023-09-21 11:10:00 2023-09-21 13:10:00+02:00 <NA>
2023-09-21 11:20:00 2023-09-21 13:20:00+02:00 <NA>
... ... ...
2023-09-27 07:20:00 2023-09-27 09:20:00+02:00 69.4
2023-09-27 07:30:00 2023-09-27 09:30:00+02:00 <NA>
2023-09-27 07:40:00 2023-09-27 09:40:00+02:00 <NA>
2023-09-27 07:50:00 2023-09-27 09:50:00+02:00 <NA>
2023-09-27 08:00:00 2023-09-27 10:00:00+02:00 77.6

561 rows × 1 columns

In [43]:
# Get only trading hours
mrkt_exhng_open_time = '8:50'
mrkt_exhng_close_time = '17:30'
optn_mrkt_price_th = optn_mrkt_price.droplevel( # unfortunately, we have to drop levels we just added to apply the useful `between_time` function.
    'gmt').between_time(mrkt_exhng_open_time, mrkt_exhng_close_time)  # Only keep trading hours. Could also be done with `optn_mrkt_price = optn_mrkt_price.loc[(optn_mrkt_price.index.strftime('%H:%M:%S') >= '07:30:00') & (optn_mrkt_price.index.strftime('%H:%M:%S') <= '22:00:00')]`.
optn_mrkt_price_th.index = pd.MultiIndex.from_tuples(
    [(i, j) for i, j in zip(
        optn_mrkt_price_th.index.tz_convert('GMT'),
        optn_mrkt_price_th.index)],
    names=["gmt", "cet"])
optn_mrkt_price_th.fillna(method='ffill', inplace=True)  # Forward Fill to populate NaN values
print(f"Our dataframe started at {str(optn_mrkt_price_th.index[0])}, \n" +
    f"and went on continuously till {str(optn_mrkt_price_th.index[-1])}, \n" +
    f"so out of trading hours rows are removed")
# optn_mrkt_price.columns = [optn_mrkt_price_th.columns[0] + "_OptPr"]
optn_mrkt_price_th
Our dataframe started at (Timestamp('2023-09-21 10:40:00+0000', tz='GMT'), Timestamp('2023-09-21 12:40:00+0200', tz='Europe/Berlin')), 
and went on continuously till (Timestamp('2023-09-27 08:00:00+0000', tz='GMT'), Timestamp('2023-09-27 10:00:00+0200', tz='Europe/Berlin')), 
so out of trading hours rows are removed
Out[43]:
STXE41500J3.EX Optn TRDPRC_1 Pr
gmt cet
2023-09-21 10:40:00+00:00 2023-09-21 12:40:00+02:00 119.0
2023-09-21 10:50:00+00:00 2023-09-21 12:50:00+02:00 119.0
2023-09-21 11:00:00+00:00 2023-09-21 13:00:00+02:00 120.8
2023-09-21 11:10:00+00:00 2023-09-21 13:10:00+02:00 120.8
2023-09-21 11:20:00+00:00 2023-09-21 13:20:00+02:00 120.8
... ... ...
2023-09-27 07:20:00+00:00 2023-09-27 09:20:00+02:00 69.4
2023-09-27 07:30:00+00:00 2023-09-27 09:30:00+02:00 69.4
2023-09-27 07:40:00+00:00 2023-09-27 09:40:00+02:00 69.4
2023-09-27 07:50:00+00:00 2023-09-27 09:50:00+02:00 69.4
2023-09-27 08:00:00+00:00 2023-09-27 10:00:00+02:00 77.6

197 rows × 1 columns

Note also that one may want to only look at 'At Option Trade' datapoints, i.e.: Implied Volatility when a trade is made for the Option, but not when none is made. For this, we will use the 'At Trade' (AT) dataframes:

In [44]:
# Get timezone of the exchange where option is traded:
AToptn_mrkt_price = _optn_mrkt_price_gmt.copy()
AToptn_mrkt_price.index = pd.MultiIndex.from_tuples(
    [(i, j) for i, j in zip(
        AToptn_mrkt_price.index,
        AToptn_mrkt_price.index.tz_localize('GMT').tz_convert('Europe/Berlin'))],
    names=["gmt", "cet"])
# Get only trading hours
AToptn_mrkt_pr_th = AToptn_mrkt_price.droplevel('gmt').between_time(mrkt_exhng_open_time, mrkt_exhng_close_time)  # Only keep trading hours. Could also be done with `optn_mrkt_price = optn_mrkt_price.loc[(optn_mrkt_price.index.strftime('%H:%M:%S') >= '07:30:00') & (optn_mrkt_price.index.strftime('%H:%M:%S') <= '22:00:00')]`.
AToptn_mrkt_pr_th.index = pd.MultiIndex.from_tuples(
    [(i, j) for i, j in zip(
        AToptn_mrkt_pr_th.index.tz_convert('GMT'),
        AToptn_mrkt_pr_th.index)],
    names=["gmt", "cet"])
AToptn_mrkt_pr_th
Out[44]:
STXE41500J3.EX Optn TRDPRC_1 Pr
gmt cet
2023-09-21 10:40:00+00:00 2023-09-21 12:40:00+02:00 119.0
2023-09-21 11:00:00+00:00 2023-09-21 13:00:00+02:00 120.8
2023-09-22 07:10:00+00:00 2023-09-22 09:10:00+02:00 106.8
2023-09-22 07:20:00+00:00 2023-09-22 09:20:00+02:00 113.4
2023-09-22 07:40:00+00:00 2023-09-22 09:40:00+02:00 111.0
2023-09-22 08:00:00+00:00 2023-09-22 10:00:00+02:00 102.3
2023-09-22 08:30:00+00:00 2023-09-22 10:30:00+02:00 109.8
2023-09-25 08:50:00+00:00 2023-09-25 10:50:00+02:00 89.9
2023-09-25 09:30:00+00:00 2023-09-25 11:30:00+02:00 94.0
2023-09-25 12:10:00+00:00 2023-09-25 14:10:00+02:00 86.0
2023-09-25 12:30:00+00:00 2023-09-25 14:30:00+02:00 85.3
2023-09-25 13:10:00+00:00 2023-09-25 15:10:00+02:00 82.1
2023-09-25 13:20:00+00:00 2023-09-25 15:20:00+02:00 83.0
2023-09-25 13:30:00+00:00 2023-09-25 15:30:00+02:00 78.0
2023-09-25 13:50:00+00:00 2023-09-25 15:50:00+02:00 86.1
2023-09-25 14:00:00+00:00 2023-09-25 16:00:00+02:00 86.3
2023-09-25 14:10:00+00:00 2023-09-25 16:10:00+02:00 86.1
2023-09-25 14:20:00+00:00 2023-09-25 16:20:00+02:00 80.6
2023-09-25 14:30:00+00:00 2023-09-25 16:30:00+02:00 80.8
2023-09-25 14:40:00+00:00 2023-09-25 16:40:00+02:00 81.0
2023-09-25 14:50:00+00:00 2023-09-25 16:50:00+02:00 78.2
2023-09-25 15:00:00+00:00 2023-09-25 17:00:00+02:00 79.4
2023-09-25 15:10:00+00:00 2023-09-25 17:10:00+02:00 85.1
2023-09-25 15:20:00+00:00 2023-09-25 17:20:00+02:00 85.9
2023-09-26 07:00:00+00:00 2023-09-26 09:00:00+02:00 71.4
2023-09-26 07:10:00+00:00 2023-09-26 09:10:00+02:00 68.2
2023-09-26 07:20:00+00:00 2023-09-26 09:20:00+02:00 64.5
2023-09-26 07:30:00+00:00 2023-09-26 09:30:00+02:00 69.0
2023-09-26 07:40:00+00:00 2023-09-26 09:40:00+02:00 68.8
2023-09-26 07:50:00+00:00 2023-09-26 09:50:00+02:00 67.8
2023-09-26 08:20:00+00:00 2023-09-26 10:20:00+02:00 74.5
2023-09-26 08:30:00+00:00 2023-09-26 10:30:00+02:00 73.4
2023-09-26 09:40:00+00:00 2023-09-26 11:40:00+02:00 71.9
2023-09-26 10:50:00+00:00 2023-09-26 12:50:00+02:00 68.6
2023-09-26 11:20:00+00:00 2023-09-26 13:20:00+02:00 70.4
2023-09-26 13:20:00+00:00 2023-09-26 15:20:00+02:00 66.3
2023-09-26 13:30:00+00:00 2023-09-26 15:30:00+02:00 69.7
2023-09-26 13:50:00+00:00 2023-09-26 15:50:00+02:00 70.2
2023-09-26 14:00:00+00:00 2023-09-26 16:00:00+02:00 68.8
2023-09-26 14:30:00+00:00 2023-09-26 16:30:00+02:00 69.6
2023-09-26 15:20:00+00:00 2023-09-26 17:20:00+02:00 67.0
2023-09-27 07:10:00+00:00 2023-09-27 09:10:00+02:00 68.6
2023-09-27 07:20:00+00:00 2023-09-27 09:20:00+02:00 69.4
2023-09-27 08:00:00+00:00 2023-09-27 10:00:00+02:00 77.6

Here, we can see that the EUERX index's exchange opens trading for Options at 8:50am and closes at 17:30. You can look a the TAS app on Workspace to check.

In [45]:
_undrlying_mrkt_pr_gmt = rd.get_history(
    universe=[underlying],
    fields=["TRDPRC_1"],
    interval="10min",
    start=start,
    end=end)
_undrlying_mrkt_pr_gmt.index = _undrlying_mrkt_pr_gmt.index.tz_localize('GMT')  # This unfortunutally has to be done for the `join` coming up to work.
_undrlying_mrkt_pr_gmt
Out[45]:
.STOXX50E TRDPRC_1
Timestamp
2023-09-20 10:30:00+00:00 4261.19
2023-09-20 10:40:00+00:00 4259.89
2023-09-20 10:50:00+00:00 4264.49
2023-09-20 11:00:00+00:00 4265.95
2023-09-20 11:10:00+00:00 4264.97
... ...
2023-09-27 07:40:00+00:00 4142.0
2023-09-27 07:50:00+00:00 4146.58
2023-09-27 08:00:00+00:00 4152.58
2023-09-27 08:10:00+00:00 4147.91
2023-09-27 08:20:00+00:00 4149.99

253 rows × 1 columns

In [46]:
ATdf_th = AToptn_mrkt_pr_th.join(
    _undrlying_mrkt_pr_gmt, how='inner', on=['gmt'])
ATdf_th.rename(
    columns={"TRDPRC_1": 'underlying ' + underlying + ' TRDPRC_1'},
    inplace=True)
ATdf_th.columns.name = instrument
ATdf_th
Out[46]:
STXE41500J3.EX Optn TRDPRC_1 Pr underlying .STOXX50E TRDPRC_1
gmt cet
2023-09-21 10:40:00+00:00 2023-09-21 12:40:00+02:00 119.0 4211.18
2023-09-21 11:00:00+00:00 2023-09-21 13:00:00+02:00 120.8 4214.9
2023-09-22 07:10:00+00:00 2023-09-22 09:10:00+02:00 106.8 4203.19
2023-09-22 07:20:00+00:00 2023-09-22 09:20:00+02:00 113.4 4192.18
2023-09-22 07:40:00+00:00 2023-09-22 09:40:00+02:00 111.0 4203.54
2023-09-22 08:00:00+00:00 2023-09-22 10:00:00+02:00 102.3 4185.43
2023-09-22 08:30:00+00:00 2023-09-22 10:30:00+02:00 109.8 4196.53
2023-09-25 08:50:00+00:00 2023-09-25 10:50:00+02:00 89.9 4168.54
2023-09-25 09:30:00+00:00 2023-09-25 11:30:00+02:00 94.0 4174.64
2023-09-25 12:10:00+00:00 2023-09-25 14:10:00+02:00 86.0 4160.07
2023-09-25 12:30:00+00:00 2023-09-25 14:30:00+02:00 85.3 4163.94
2023-09-25 13:10:00+00:00 2023-09-25 15:10:00+02:00 82.1 4155.8
2023-09-25 13:20:00+00:00 2023-09-25 15:20:00+02:00 83.0 4151.64
2023-09-25 13:30:00+00:00 2023-09-25 15:30:00+02:00 78.0 4148.09
2023-09-25 13:50:00+00:00 2023-09-25 15:50:00+02:00 86.1 4161.76
2023-09-25 14:00:00+00:00 2023-09-25 16:00:00+02:00 86.3 4159.83
2023-09-25 14:10:00+00:00 2023-09-25 16:10:00+02:00 86.1 4158.64
2023-09-25 14:20:00+00:00 2023-09-25 16:20:00+02:00 80.6 4151.65
2023-09-25 14:30:00+00:00 2023-09-25 16:30:00+02:00 80.8 4149.35
2023-09-25 14:40:00+00:00 2023-09-25 16:40:00+02:00 81.0 4152.98
2023-09-25 14:50:00+00:00 2023-09-25 16:50:00+02:00 78.2 4149.09
2023-09-25 15:00:00+00:00 2023-09-25 17:00:00+02:00 79.4 4154.15
2023-09-25 15:10:00+00:00 2023-09-25 17:10:00+02:00 85.1 4162.69
2023-09-25 15:20:00+00:00 2023-09-25 17:20:00+02:00 85.9 4164.79
2023-09-26 07:00:00+00:00 2023-09-26 09:00:00+02:00 71.4 4133.08
2023-09-26 07:10:00+00:00 2023-09-26 09:10:00+02:00 68.2 4125.91
2023-09-26 07:20:00+00:00 2023-09-26 09:20:00+02:00 64.5 4117.3
2023-09-26 07:30:00+00:00 2023-09-26 09:30:00+02:00 69.0 4127.81
2023-09-26 07:40:00+00:00 2023-09-26 09:40:00+02:00 68.8 4130.7
2023-09-26 07:50:00+00:00 2023-09-26 09:50:00+02:00 67.8 4129.2
2023-09-26 08:20:00+00:00 2023-09-26 10:20:00+02:00 74.5 4142.31
2023-09-26 08:30:00+00:00 2023-09-26 10:30:00+02:00 73.4 4142.35
2023-09-26 09:40:00+00:00 2023-09-26 11:40:00+02:00 71.9 4135.33
2023-09-26 10:50:00+00:00 2023-09-26 12:50:00+02:00 68.6 4133.42
2023-09-26 11:20:00+00:00 2023-09-26 13:20:00+02:00 70.4 4134.98
2023-09-26 13:20:00+00:00 2023-09-26 15:20:00+02:00 66.3 4125.89
2023-09-26 13:30:00+00:00 2023-09-26 15:30:00+02:00 69.7 4135.39
2023-09-26 13:50:00+00:00 2023-09-26 15:50:00+02:00 70.2 4135.74
2023-09-26 14:00:00+00:00 2023-09-26 16:00:00+02:00 68.8 4129.84
2023-09-26 14:30:00+00:00 2023-09-26 16:30:00+02:00 69.6 4135.19
2023-09-26 15:20:00+00:00 2023-09-26 17:20:00+02:00 67.0 4128.35
2023-09-27 07:10:00+00:00 2023-09-27 09:10:00+02:00 68.6 4133.34
2023-09-27 07:20:00+00:00 2023-09-27 09:20:00+02:00 69.4 4146.47
2023-09-27 08:00:00+00:00 2023-09-27 10:00:00+02:00 77.6 4152.58

Let's put it all in one data-frame, df. Some datasets will have data going from the time we sort for start all the way to end. Some won't because no trade happened in the past few minutes/hours. We ought to base ourselves on the dataset with values getting closer to end and ffill for the other column. As a result, the following if loop is needed:

In [47]:
df = optn_mrkt_price_th.join(
    _undrlying_mrkt_pr_gmt, how='left', on=['gmt'])
df.rename(
    columns={"TRDPRC_1": 'underlying ' + underlying + ' TRDPRC_1'},
    inplace=True)
df.fillna(method='ffill', inplace=True)
df.fillna(method='bfill', inplace=True)  # Here we backfill in case `undrlying_mrkt_pr_gmt` didn't have a value for the 1st few rows of `optn_mrkt_price_th`.
df.columns.name = instrument
# df[df['underlying ' + underlying + ' TRDPRC_1'].isna()]  # Checking for NAs
df_th = df.droplevel('gmt').between_time(mrkt_exhng_open_time, mrkt_exhng_close_time)  # Only keep trading hours.
df_th.index = pd.MultiIndex.from_tuples(
    [(i, j) for i, j in zip(df_th.index.tz_convert('GMT'),
                            df_th.index)],
    names=["gmt", "cet"])
df_th
Out[47]:
STXE41500J3.EX Optn TRDPRC_1 Pr underlying .STOXX50E TRDPRC_1
gmt cet
2023-09-21 10:40:00+00:00 2023-09-21 12:40:00+02:00 119.0 4211.18
2023-09-21 10:50:00+00:00 2023-09-21 12:50:00+02:00 119.0 4214.46
2023-09-21 11:00:00+00:00 2023-09-21 13:00:00+02:00 120.8 4214.9
2023-09-21 11:10:00+00:00 2023-09-21 13:10:00+02:00 120.8 4215.11
2023-09-21 11:20:00+00:00 2023-09-21 13:20:00+02:00 120.8 4213.9
... ... ... ...
2023-09-27 07:20:00+00:00 2023-09-27 09:20:00+02:00 69.4 4146.47
2023-09-27 07:30:00+00:00 2023-09-27 09:30:00+02:00 69.4 4138.24
2023-09-27 07:40:00+00:00 2023-09-27 09:40:00+02:00 69.4 4142.0
2023-09-27 07:50:00+00:00 2023-09-27 09:50:00+02:00 69.4 4146.58
2023-09-27 08:00:00+00:00 2023-09-27 10:00:00+02:00 77.6 4152.58

197 rows × 2 columns

In [48]:
# if optn_mrkt_price_th.index[-1][1] >= _undrlying_mrkt_pr_gmt.index[-1]:
#     df = optn_mrkt_price.copy()
#     df['underlying ' + underlying + ' TRDPRC_1'] = _undrlying_mrkt_pr_gmt
# else:
#     df = _undrlying_mrkt_pr_gmt.copy()
#     df.rename(columns={"TRDPRC_1": 'underlying ' + underlying + ' TRDPRC_1'}, inplace=True)
#     df['TRDPRC_1'] = optn_mrkt_price
#     df.columns.name = optn_mrkt_price.columns.name
# df.fillna(method='ffill', inplace=True)  # Forward Fill to populate NaN values
# df = df.dropna()
# df

Strike Price¶

In [49]:
strikePrice = closest_atm_optn.StrikePrice.values[0]
In [50]:
strikePrice
Out[50]:
4150

Risk-Free Interest Rate¶

In [51]:
(datetime.strptime(start, '%Y-%m-%dT%H:%M:%S.%f') - timedelta(days=1)).strftime('%Y-%m-%d')
Out[51]:
'2023-09-19'
In [52]:
start
Out[52]:
'2023-09-20T10:27:45.460240'
In [53]:
_eur_rf_rate = rd.get_history(  # `rd.get_history` provides data in the GMT timezone
    universe=['EURIBOR3MD='],  # USD3MFSR=, USDSOFR=
    fields=['TR.FIXINGVALUE'],
    # Since we will use `dropna()` as a way to select the rows we are after later on in the code, we need to ask for more risk-free data than needed, just in case we don't have enough:
    start=(datetime.strptime(start, '%Y-%m-%dT%H:%M:%S.%f') - timedelta(days=1)).strftime('%Y-%m-%d'),
    end=(datetime.strptime(end, '%Y-%m-%dT%H:%M:%S.%f') + timedelta(days=1)).strftime('%Y-%m-%d'))
_eur_rf_rate
Out[53]:
EURIBOR3MD= Fixing Value
Date
2023-09-19 3.934
2023-09-20 3.934
2023-09-21 3.955
2023-09-22 3.958
2023-09-25 3.977
2023-09-26 3.941

Euribor values are released daily at 11am CET, and it is published as such on Refinitiv:

In [54]:
eur_rf_rate = _eur_rf_rate.resample('10Min').mean().fillna(method='ffill')
eur_rf_rate.index = eur_rf_rate.index.tz_localize('GMT')  # This unfortunutally has to be done for the `join` coming up to work.
eur_rf_rate.rename(
    columns={"Fixing Value": "EurRfRate"},
    inplace=True)

You might be running your code after the latest Risk Free Rate published, so the most accurate such value after taht would be the latest value, thus the use of ffill:

In [55]:
df_th = df_th.join(eur_rf_rate, how='left', on=['gmt'])  # `rd.get_history` provides data in the GMT timezone, and it was used to get `eur_rf_rate`.
df_th.fillna(method='ffill', inplace=True)
df_th.columns.name = instrument
df_th.rename(columns={"Fixing Value": "EurRfRate"},
          inplace=True)
df_th
Out[55]:
STXE41500J3.EX Optn TRDPRC_1 Pr underlying .STOXX50E TRDPRC_1 EurRfRate
gmt cet
2023-09-21 10:40:00+00:00 2023-09-21 12:40:00+02:00 119.0 4211.18 3.955
2023-09-21 10:50:00+00:00 2023-09-21 12:50:00+02:00 119.0 4214.46 3.955
2023-09-21 11:00:00+00:00 2023-09-21 13:00:00+02:00 120.8 4214.9 3.955
2023-09-21 11:10:00+00:00 2023-09-21 13:10:00+02:00 120.8 4215.11 3.955
2023-09-21 11:20:00+00:00 2023-09-21 13:20:00+02:00 120.8 4213.9 3.955
... ... ... ... ...
2023-09-27 07:20:00+00:00 2023-09-27 09:20:00+02:00 69.4 4146.47 3.977
2023-09-27 07:30:00+00:00 2023-09-27 09:30:00+02:00 69.4 4138.24 3.977
2023-09-27 07:40:00+00:00 2023-09-27 09:40:00+02:00 69.4 4142.0 3.977
2023-09-27 07:50:00+00:00 2023-09-27 09:50:00+02:00 69.4 4146.58 3.977
2023-09-27 08:00:00+00:00 2023-09-27 10:00:00+02:00 77.6 4152.58 3.977

197 rows × 3 columns

Now for the At Trade dataframe:

In [56]:
ATdf_th = ATdf_th.join(eur_rf_rate, how='left', on=['gmt'])
ATdf_th.fillna(method='ffill', inplace=True)
ATdf_th.columns.name = instrument
ATdf_th
Out[56]:
STXE41500J3.EX Optn TRDPRC_1 Pr underlying .STOXX50E TRDPRC_1 EurRfRate
gmt cet
2023-09-21 10:40:00+00:00 2023-09-21 12:40:00+02:00 119.0 4211.18 3.955
2023-09-21 11:00:00+00:00 2023-09-21 13:00:00+02:00 120.8 4214.9 3.955
2023-09-22 07:10:00+00:00 2023-09-22 09:10:00+02:00 106.8 4203.19 3.958
2023-09-22 07:20:00+00:00 2023-09-22 09:20:00+02:00 113.4 4192.18 3.958
2023-09-22 07:40:00+00:00 2023-09-22 09:40:00+02:00 111.0 4203.54 3.958
2023-09-22 08:00:00+00:00 2023-09-22 10:00:00+02:00 102.3 4185.43 3.958
2023-09-22 08:30:00+00:00 2023-09-22 10:30:00+02:00 109.8 4196.53 3.958
2023-09-25 08:50:00+00:00 2023-09-25 10:50:00+02:00 89.9 4168.54 3.977
2023-09-25 09:30:00+00:00 2023-09-25 11:30:00+02:00 94.0 4174.64 3.977
2023-09-25 12:10:00+00:00 2023-09-25 14:10:00+02:00 86.0 4160.07 3.977
2023-09-25 12:30:00+00:00 2023-09-25 14:30:00+02:00 85.3 4163.94 3.977
2023-09-25 13:10:00+00:00 2023-09-25 15:10:00+02:00 82.1 4155.8 3.977
2023-09-25 13:20:00+00:00 2023-09-25 15:20:00+02:00 83.0 4151.64 3.977
2023-09-25 13:30:00+00:00 2023-09-25 15:30:00+02:00 78.0 4148.09 3.977
2023-09-25 13:50:00+00:00 2023-09-25 15:50:00+02:00 86.1 4161.76 3.977
2023-09-25 14:00:00+00:00 2023-09-25 16:00:00+02:00 86.3 4159.83 3.977
2023-09-25 14:10:00+00:00 2023-09-25 16:10:00+02:00 86.1 4158.64 3.977
2023-09-25 14:20:00+00:00 2023-09-25 16:20:00+02:00 80.6 4151.65 3.977
2023-09-25 14:30:00+00:00 2023-09-25 16:30:00+02:00 80.8 4149.35 3.977
2023-09-25 14:40:00+00:00 2023-09-25 16:40:00+02:00 81.0 4152.98 3.977
2023-09-25 14:50:00+00:00 2023-09-25 16:50:00+02:00 78.2 4149.09 3.977
2023-09-25 15:00:00+00:00 2023-09-25 17:00:00+02:00 79.4 4154.15 3.977
2023-09-25 15:10:00+00:00 2023-09-25 17:10:00+02:00 85.1 4162.69 3.977
2023-09-25 15:20:00+00:00 2023-09-25 17:20:00+02:00 85.9 4164.79 3.977
2023-09-26 07:00:00+00:00 2023-09-26 09:00:00+02:00 71.4 4133.08 3.977
2023-09-26 07:10:00+00:00 2023-09-26 09:10:00+02:00 68.2 4125.91 3.977
2023-09-26 07:20:00+00:00 2023-09-26 09:20:00+02:00 64.5 4117.3 3.977
2023-09-26 07:30:00+00:00 2023-09-26 09:30:00+02:00 69.0 4127.81 3.977
2023-09-26 07:40:00+00:00 2023-09-26 09:40:00+02:00 68.8 4130.7 3.977
2023-09-26 07:50:00+00:00 2023-09-26 09:50:00+02:00 67.8 4129.2 3.977
2023-09-26 08:20:00+00:00 2023-09-26 10:20:00+02:00 74.5 4142.31 3.977
2023-09-26 08:30:00+00:00 2023-09-26 10:30:00+02:00 73.4 4142.35 3.977
2023-09-26 09:40:00+00:00 2023-09-26 11:40:00+02:00 71.9 4135.33 3.977
2023-09-26 10:50:00+00:00 2023-09-26 12:50:00+02:00 68.6 4133.42 3.977
2023-09-26 11:20:00+00:00 2023-09-26 13:20:00+02:00 70.4 4134.98 3.977
2023-09-26 13:20:00+00:00 2023-09-26 15:20:00+02:00 66.3 4125.89 3.977
2023-09-26 13:30:00+00:00 2023-09-26 15:30:00+02:00 69.7 4135.39 3.977
2023-09-26 13:50:00+00:00 2023-09-26 15:50:00+02:00 70.2 4135.74 3.977
2023-09-26 14:00:00+00:00 2023-09-26 16:00:00+02:00 68.8 4129.84 3.977
2023-09-26 14:30:00+00:00 2023-09-26 16:30:00+02:00 69.6 4135.19 3.977
2023-09-26 15:20:00+00:00 2023-09-26 17:20:00+02:00 67.0 4128.35 3.977
2023-09-27 07:10:00+00:00 2023-09-27 09:10:00+02:00 68.6 4133.34 3.977
2023-09-27 07:20:00+00:00 2023-09-27 09:20:00+02:00 69.4 4146.47 3.977
2023-09-27 08:00:00+00:00 2023-09-27 10:00:00+02:00 77.6 4152.58 3.977
In [57]:
# pd.options.mode.chained_assignment = None  # default='warn'
# AT_undrlying_mrkt_pr_gmt['EurRfRate'] = [pd.NA for i in AT_undrlying_mrkt_pr_gmt.index]
# for i in _eur_rf_rate.index:
#     _i = str(i)[:10]
#     for n, j in enumerate(AT_undrlying_mrkt_pr_gmt.index):
#         if _i in str(j):
#             if len(_eur_rf_rate.loc[i].values) == 2:
#                 AT_undrlying_mrkt_pr_gmt['EurRfRate'].iloc[n] = _eur_rf_rate.loc[i].values[0][0]
#             elif len(_eur_rf_rate.loc[i].values) == 1:
#                 AT_undrlying_mrkt_pr_gmt['EurRfRate'].iloc[n] = _eur_rf_rate.loc[i].values[0]
# ATdf = AT_undrlying_mrkt_pr_gmt.copy()
In [58]:
# ATdf = ATdf.fillna(method='ffill')
# ATdf.head(2)

Annualized Continuous Dividend Rate¶

We are going to assume no dividends.

Calculating IV¶

On the Developer Portal, one can see documentation about the Instrument Pricing Analytics service that allows access to calculating functions (that use to be called 'AdFin'). This service is accessible via several RESTful endpoints (in a family of endpoints called 'Quantitative Analytics') which can be used via RD. However, while we are going to build towards a Class that will put all our concepts together, I 1st want to showcase the several ways in which we can collect the data we're are after, for (i) all trades & (ii) at option trades only (i.e.: not every trade of the underlying) and (a) using the RD delivery layer & (b) the RD content layer:

Data returned this far was time-stamped in the GMT Time Zone, we need to re-calibrate it to the timezone of our machine:

All Trades¶

In [59]:
type(test_df.index)
Out[59]:
pandas.core.indexes.range.RangeIndex
In [60]:
request_fields = [  # This is the 'normal' set of fields.
    "MarketValueInDealCcy", "RiskFreeRatePercent",
    "UnderlyingPrice", "PricingModelType",
    "DividendType", "UnderlyingTimeStamp",
    "ReportCcy", "VolatilityType",
    "Volatility", "DeltaPercent",
    "GammaPercent", "RhoPercent",
    "ThetaPercent", "VegaPercent"]
request_fields = sorted([  # This is the 'troubleshooting' set of fields.
    'ErrorMessage', 'InstrumentTag', 'InstrumentCode', 'InstrumentDescription', 'EndDate', 'OptionType', 'ExerciseStyle', 'CallPut', 'DealCcy', 'LotSize', 'LotsUnits', 'UnderlyingRIC', 'UnderlyingCcy', 'ValuationDate', 'MarketDataDate', 'DaysToExpiry', 'YearsToExpiry', 'Strike', 'OptionPrice', 'OptionPriceSide', 'OptionTimeStamp', 'UnderlyingPrice', 'UnderlyingPriceSide', 'UnderlyingTimeStamp', 'PricingModelTypeList', 'PricingModelType', 'VolatilityPercent', 'VolatilityType', 'Volatility', 'DailyVolatility', 'DailyVolatilityPercent', 'PremiumPercent', 'RiskFreeRatePercent', 'DividendYieldPercent', 'ForecastDividendYieldPercent', 'DividendType', 'MarketValueInDealCcy', 'TotalMarketValueInDealCcy', 'TotalMarketValueInReportCcy', 'TotalMarketValueInDealCcy', 'TotalMarketValueInReportCcy', 'FixingStartDate', 'FixingEndDate', 'FixingFrequency', 'FixingCalendar', 'AverageType', 'AverageSoFar', 'FixingDateArray', 'FixingNumbers', 'BarrierType', 'BarrierLevel', 'CbbcType', 'CbbcOptionType', 'ConversionRatio', 'ResidualAmountInDealCcy', 'ResidualAmountInReportCcy', 'DeltaPercent', 'GammaPercent', 'RhoPercent', 'ThetaPercent', 'VegaPercent', 'HedgeRatio', 'DeltaExposureInDealCcy', 'DeltaExposureInReportCcy', 'DeltaHedgePositionInDealCcy', 'DeltaHedgePositionInReportCcy', 'Gearing', 'Leverage', 'TimeValueInDealCcy', 'TimeValueInReportCcy', 'TimeValuePercent', 'TimeValuePerDay', 'MoneynessAmountInDealCcy', 'MoneynessAmountInReportCcy', 'BreakEvenPriceInDealCcy', 'BreakEvenPriceInReportCcy', 'PremiumOverCashInDealCcy', 'PremiumOverCashInReportCcy', 'PremiumOverCashPercent', 'PremiumPerAnnumInDealCcy', 'PremiumPerAnnumInReportCcy', 'PremiumPerAnnumPercent', 'DeltaAmountInDealCcy', 'DeltaAmountInReportCcy', 'BreakEvenDeltaAmountInDealCcy', 'BreakEvenDeltaAmountInReportCcy', 'GammaAmountInDealCcy', 'GammaAmountInReportCcy', 'RhoAmountInDealCcy', 'RhoAmountInReportCcy', 'ThetaAmountInDealCcy', 'ThetaAmountInReportCcy', 'SevenDaysThetaPercent', 'SevenDaysThetaAmountInDealCcy', 'SevenDaysThetaAmountInReportCcy', 'VegaAmountInDealCcy', 'VegaAmountInReportCcy', 'DvegaDtimeAmountInDealCcy', 'DvegaDtimeAmountInReportCcy', 'VannaAmountInDealCcy', 'VannaAmountInReportCcy', 'VolgaAmountInDealCcy', 'VolgaAmountInReportCcy', 'SpeedAmountInDealCcy', 'SpeedAmountInReportCcy', 'CharmAmountInDealCcy', 'CharmAmountInReportCcy', 'ColorAmountInDealCcy', 'ColorAmountInReportCcy', 'ZommaAmountInDealCcy', 'ZommaAmountInReportCcy', 'UltimaAmountInDealCcy', 'UltimaAmountInReportCcy'])

Delivery Layer¶

Now for the At Trade dataframe:

In [61]:
universeL = [
    {
        "instrumentType": "Option",
        "instrumentDefinition": {
            "buySell": "Buy",
            "underlyingType": "Eti",
            "instrumentCode": instrument,
            "strike": str(strikePrice),
          },
          "pricingParameters": {
            "valuationDate": str(  # IPA works on GMT timezone, so let's stick to it.
              df_th.index[i][0]).replace(" ", "T").split("+", 1)[0] + "Z", # optional, date-time # The original `df_th` index provides dates in the form of '2023-06-15 15:40:00+01:00', so here we (i) change spaces with T, (ii) keep only the part of the str before the "+" and (iii) add "Z" at the end. N.B.: Another way to get the right timezone would be `str([i for i in df_th.index[0] if str(i.tz) == 'GMT'][0].replace(" ", "T").split("+", 1)[0] + "Z")`
            "marketValueInDealCcy": str(df_th.iloc[:,0][i]),
            "riskFreeRatePercent": str(df_th['EurRfRate'][i]),
            "underlyingPrice": str(
              df_th['underlying ' + underlying + ' TRDPRC_1'][i]),
            "pricingModelType": "BlackScholes",
            "dividendType": "ImpliedYield",
            "volatilityType": "Implied",
            "underlyingTimeStamp": "Default",
            "reportCcy": "EUR"
          }
        }
      for i in range(len(df_th))]

IPA can only accept up to 100 request at a time. If there are more elements in our request list, we need to 'chunk' it:

In [62]:
def Chunks(lst, n):
    """Yield successive n-sized chunks from lst."""
    for i in range(0, len(lst), n):
        yield lst[i:i + n]

This is the cell, next coming up below, that has a rather high chance of failing. This is because there is no error handling of any kind, just in case there are issues on the servers where we are retreiving data. The Content Layer functions do have such error handing steps, and therefore is considerably less likely to fail or run into errors.

In [63]:
def Fin_Contracts_Chunk_Calls_Delivery_Lay(
        req_univ,
        batch_of=100,
        dl=rd,
        endpoint='https://api.refinitiv.com/data/quantitative-analytics/v1/financial-contracts',
        request_fields=[
            "MarketValueInDealCcy", "RiskFreeRatePercent", "UnderlyingPrice", "PricingModelType",
            "DividendType", "UnderlyingTimeStamp", "ReportCcy", "VolatilityType", "Volatility",
            "DeltaPercent", "GammaPercent", "RhoPercent", "ThetaPercent", "VegaPercent"],
        request_outputs=["Data", "Headers"],
        print_log=True):
    """
    Fin_Contracts_Chunk_Calls V1.0: Financial Contracts Chunk Calls via the Delivery Layer python function Version 1.0:
        This function was created to send large requests (more than 100 calls) to the IPA 
        (Instrument Pricing Analytics) a.k.a. (also known as) QA (Quantitative Analytics) endpoint called
        'financial-contracts'. However, it may be used for other endpoints.
        This function uses the Refinitiv Delivery Layer; more on that here:
        https://developers.refinitiv.com/en/article-catalog/article/summary-of-common-lseg-refinitiv-apis
        

    Dependencies
    ----------------------------------------------
    Python library 'refinitiv-data' version 1.0.2

    Parameters
    -----------------------------------------------
    Input:
        req_univ(list of str): List of RICs (Refinitiv Identifier Codes) for the request.
        Example: req_univ=[{'instrumentType': 'Option',
            'instrumentDefinition': {'buySell': 'Buy', 'underlyingType': 'Eti',
            'instrumentCode': 'STXE42750G3.EX','strike': '4275'},
            'pricingParameters': {'valuationDate': '2023-06-15T15:40:00Z',
            'marketValueInDealCcy': '130.1', 'riskFreeRatePercent': '3.547',
            'underlyingPrice': '4363.44', 'pricingModelType': 'BlackScholes',
            'dividendType': 'ImpliedYield', 'volatilityType': 'Implied',
            'underlyingTimeStamp': 'Default', 'reportCcy': 'EUR'}}]

        batch_of(int): The max number of instrument in the 'universe' request. This can only be `100` max.
            Default: batch_of=100

        dl(module): The instance of the Data Library (Python RD Lib. (Refinitif Data library)) you are using,
            after being authenticated. For more info on the RD Lib., see:
            https://developers.refinitiv.com/en/api-catalog/refinitiv-data-platform/refinitiv-data-library-for-python
            Default: dl=rd

        endpoint(str): The URL for the endpoint you are trying to ping on RDP (Refinitiv Data Platform).
            More info on this here: https://apidocs.refinitiv.com/Apps/ApiDocs
            Default: endpoint='https://api.refinitiv.com/data/quantitative-analytics/v1/financial-contracts'

        request_fields(list of str): List of the fileds you are after. More info here:
            https://apidocs.refinitiv.com/Apps/ApiDocs
            Default: request_fields=[
                "MarketValueInDealCcy", "RiskFreeRatePercent", "UnderlyingPrice", "PricingModelType",
                "DividendType", "UnderlyingTimeStamp", "ReportCcy", "VolatilityType", "Volatility",
                "DeltaPercent", "GammaPercent", "RhoPercent", "ThetaPercent", "VegaPercent"]

        request_outputs(list of str): List of the headers you are after. More info here:
            https://apidocs.refinitiv.com/Apps/ApiDocs
            Default: request_outputs=["Data", "Headers"]

        print_log(bool): If `True`, then batch numbers are printed; they are not if print_log is set to `False`.
            Default: print_log=True
    """

    def Chunks(lst, n):
        """Yield successive n-sized chunks from lst."""
        for i in range(0, len(lst), n):
            yield lst[i:i + n]

    for i, j in enumerate(Chunks(req_univ, batch_of)):
        if print_log:
            print(f"Batch of {batch_of} requests no. {str(i+1)}/{str(len([i for i in Chunks(req_univ, batch_of)]))} started")
        request_definition = dl.delivery.endpoint_request.Definition(
            method=dl.delivery.endpoint_request.RequestMethod.POST,
            url=endpoint,
            body_parameters={
                "fields": request_fields,
                "outputs": request_outputs,
                "universe": j})
        
        resp = request_definition.get_data()
        if resp.is_success:
            headers_name = [h['name'] for h in resp.data.raw['headers']]
            if i == 0:
                resp_df = pd.DataFrame(
                    data=resp.data.raw['data'], columns=headers_name)
            else:
                _resp_df = pd.DataFrame(
                    data=resp.data.raw['data'], columns=headers_name)
                resp_df = _resp_df.append(_resp_df, ignore_index=True)
            if print_log:
                print(f"Batch of {batch_of} requests no. {str(i+1)}/{str(len([i for i in Chunks(req_univ, batch_of)]))} ended")
        else:
            display(resp)

    return resp_df
In [64]:
response3df = Fin_Contracts_Chunk_Calls_Delivery_Lay(
    req_univ=universeL, request_fields=request_fields)
Batch of 100 requests no. 1/2 started
Batch of 100 requests no. 1/2 ended
Batch of 100 requests no. 2/2 started
Batch of 100 requests no. 2/2 ended
In [65]:
response3df.tail(3)
Out[65]:
AverageSoFar AverageType BarrierLevel BarrierType BreakEvenDeltaAmountInDealCcy BreakEvenDeltaAmountInReportCcy BreakEvenPriceInDealCcy BreakEvenPriceInReportCcy CallPut CbbcOptionType ... VegaAmountInReportCcy VegaPercent Volatility VolatilityPercent VolatilityType VolgaAmountInDealCcy VolgaAmountInReportCcy YearsToExpiry ZommaAmountInDealCcy ZommaAmountInReportCcy
191 None NaN None 0.355624 0.355624 4219.4 4219.4 CALL ... 41.166987 4.116699 16.692590 16.692590 Calculated -0.919557 -0.919557 0.063014 -0.013864 -0.013864
192 None NaN None 0.360421 0.360421 4219.4 4219.4 CALL ... 41.159331 4.115933 16.120708 16.120708 Calculated 2.189009 2.189009 0.063014 -0.014816 -0.014816
193 None NaN None 0.366663 0.366663 4227.6 4227.6 CALL ... 41.116616 4.111662 17.345784 17.345784 Calculated 9.356929 9.356929 0.063014 -0.012711 -0.012711

3 rows × 113 columns

Content Layer¶

As may (or may not) have been apparent aboe, the delivery layer does not offer any error hendling management. The server where we're requestig for data may be busy, so we may get unsuccessful messages back. You could build error handing logic yourself, but let's not reinvent the wheel when the RD Python Library exists!

In [66]:
def Fin_Contracts_Chunk_Calls_Content_Lay(
        req_univ,
        batch_of=100,
        rdf=rdf,
        request_fields=[
                "MarketValueInDealCcy", "RiskFreeRatePercent", "UnderlyingPrice", "PricingModelType",
                "DividendType", "UnderlyingTimeStamp", "ReportCcy", "VolatilityType", "Volatility",
                "DeltaPercent", "GammaPercent", "RhoPercent", "ThetaPercent", "VegaPercent"],
        print_log=True):
    """
    Fin_Contracts_Chunk_Calls_Content_Lay V1.0: Financial Contracts Chunk Calls via the Content Layer python function Version 1.0:
        This function was created to send large requests (more than 100 calls) to the IPA 
        (Instrument Pricing Analytics) a.k.a. (also known as) QA (Quantitative Analytics) endpoint called
        'financial-contracts'.
        This function uses the Refinitiv Content Layer; more on that here:
        https://developers.refinitiv.com/en/article-catalog/article/summary-of-common-lseg-refinitiv-apis

    Dependencies
    ----------------------------------------------
    Python library 'refinitiv-data' version 1.0.2
    Module financial_contracts via `import refinitiv.data.content.ipa.financial_contracts as rdf`

    Parameters
    -----------------------------------------------
    Input:
        req_univ(list of str): List of RICs (Refinitiv Identifier Codes) for the request.
        Example:
            from refinitiv.data.content.ipa.financial_contracts import option
            lis = [option.Definition(
                underlying_type=option.UnderlyingType.ETI,
                buy_sell='Buy',
                instrument_code='STXE42750G3.EX',
                strike=float(4275),
                pricing_parameters=option.PricingParameters(
                    valuation_date='2023-06-21T10:30:00Z',
                    market_value_in_deal_ccy=float(110),
                    risk_free_rate_percent=3.568,
                    underlying_price=float(4346.11),
                    pricing_model_type='BlackScholes',
                    volatility_type='Implied',
                    underlying_time_stamp='Default',
                    report_ccy='EUR'))]
            req_univ=lis

        batch_of(int): The max number of instrument in the 'universe' request. This can only be `100` max.
            Default: batch_of=100

        rd(module): The instance of the Python RD Lib. (Refinitif Data library) you are using,
            after being authenticated. For more info on the RD Lib., see:
            https://developers.refinitiv.com/en/api-catalog/refinitiv-data-platform/refinitiv-data-library-for-python
            Default: rd=rd

        request_fields(list of str): List of the fileds you are after. More info here:
            https://apidocs.refinitiv.com/Apps/ApiDocs
            Default: request_fields=[
                "MarketValueInDealCcy", "RiskFreeRatePercent", "UnderlyingPrice", "PricingModelType",
                "DividendType", "UnderlyingTimeStamp", "ReportCcy", "VolatilityType", "Volatility",
                "DeltaPercent", "GammaPercent", "RhoPercent", "ThetaPercent", "VegaPercent"]

        print_log(bool): If `True`, then batch numbers are printed; they are not if print_log is set to `False`.
            Default: print_log=True
    """

    for i, j in enumerate(Chunks(req_univ, batch_of)):
        if print_log:
            print(f"Batch of {len(j)} requests no. {i+1}/{len([i for i in Chunks(req_univ, batch_of)])} started")
        resp = rdf.Definitions(universe=j, fields=request_fields)
        resp = resp.get_data()
        if i == 0:
            resp_df = resp.data.df
        else:
            resp_df = resp_df.append(resp.data.df, ignore_index=True)
        if print_log:
            # print(resp.request_message)
            # print(resp.http_response)
            # print(resp.__dict__)
            print(f"Batch of {len(j)} requests no. {i+1}/{len([i for i in Chunks(req_univ, batch_of)])} ended")

    return resp_df
Test¶
In [67]:
# CuniverseL_test_1 = [  # C here is for the fact that we're using the content layer
#     option.Definition(
#         underlying_type=option.UnderlyingType.ETI,
#         buy_sell='Buy',
#         # instrument_code=instrument,
#         strike=float(strikePrice),
#         pricing_parameters=option.PricingParameters(
#             valuation_date=str(dfLocalTimeZone.index[i]).replace(" ", "T").split("+", 1)[0] + "Z",  # optional, str # The date at which the instrument is valued. The value is expressed in iso 8601 format: yyyy-mm-ddt[hh]:[mm]:[ss]z (e.g., '2021-01-01t00:00:00z'). by default, marketdatadate is used. If marketdatadate is not specified, the default value is today.
#             market_value_in_deal_ccy=float(dfLocalTimeZone[LocalTimeZone_udrly_pr_f][i]),
#             risk_free_rate_percent=float(dfLocalTimeZone['EurRfRate'][i]),
#             underlying_price=float(dfLocalTimeZone[
#                 'underlying ' + underlying + ' TRDPRC_1'][i]),
#             pricing_model_type='BlackScholes',
#             volatility_type='Implied',
#             underlying_time_stamp='Default',
#             report_ccy='EUR'))
#     for i in range(63, 68)]
In [68]:
# [{
#         "instrumentType": "Option",
#         "instrumentDefinition": {
#             "buySell": "Buy",
#             "underlyingType": "Eti",
#             # "instrumentCode": instrument,
#             "strike": str(strikePrice)},
#             "pricingParameters": {
#                 "valuationDate": str(dfLocalTimeZone.index[i]).replace(" ", "T").split("+", 1)[0] + "Z", # optional, date-time # The original `dfLocalTimeZone` index provides dates in the form of '2023-06-15 15:40:00+01:00', so here we (i) change spaces with T, (ii) keep only the part of the str before the "+" and (iii) add "Z" at the end.
#                 "marketValueInDealCcy": float(dfLocalTimeZone[LocalTimeZone_udrly_pr_f][i]),
#                 "riskFreeRatePercent": float(dfLocalTimeZone['EurRfRate'][i]),
#                 "underlyingPrice": float(dfLocalTimeZone['underlying ' + underlying + ' TRDPRC_1'][i]),
#                 "pricingModelType": "BlackScholes",
#                 "dividendType": "ImpliedYield",
#                 "volatilityType": "Implied",
#                 "underlyingTimeStamp": "Default",
#                 "reportCcy": "EUR"}}
#         for i in range(63, 68)]
In [69]:
# con_lay_test_1 = Fin_Contracts_Chunk_Calls_Content_Lay(
#     req_univ=CuniverseL_test_1, request_fields=request_fields, print_log=True)
In [70]:
# con_lay_test_1.index = dfLocalTimeZone.index[63:68]
In [71]:
# con_lay_test_1[[
#     'Volatility', 'MarketValueInDealCcy',
#     'RiskFreeRatePercent', 'UnderlyingPrice', 'DeltaPercent',
#     'GammaPercent', 'RhoPercent', 'ThetaPercent', 'VegaPercent']]
Content Layer¶
In [72]:
df_th.iloc[:,0][9]
Out[72]:
120.8
In [73]:
CuniverseL = [  # C here is for the fact that we're using the content layer
    option.Definition(
        underlying_type=option.UnderlyingType.ETI,
        buy_sell='Buy',
        instrument_code=instrument,
        strike=float(strikePrice),
        pricing_parameters=option.PricingParameters(
            valuation_date=str(df_th.index[i][0]).replace(" ", "T").split("+", 1)[0] + "Z",  # optional, str # The date at which the instrument is valued. The value is expressed in iso 8601 format: yyyy-mm-ddt[hh]:[mm]:[ss]z (e.g., '2021-01-01t00:00:00z'). by default, marketdatadate is used. If marketdatadate is not specified, the default value is today.
            market_value_in_deal_ccy=float(df_th.iloc[:,0][i]),
            risk_free_rate_percent=float(df_th['EurRfRate'][i]),
            underlying_price=float(df_th[
                'underlying ' + underlying + ' TRDPRC_1'][i]),
            pricing_model_type='BlackScholes',
            volatility_type='Implied',
            underlying_time_stamp='Default',
            report_ccy='EUR'))
    for i in range(len(df_th))]
In [74]:
response4df = Fin_Contracts_Chunk_Calls_Content_Lay(
    req_univ=CuniverseL, request_fields=request_fields)
Batch of 100 requests no. 1/2 started
Batch of 100 requests no. 1/2 ended
Batch of 97 requests no. 2/2 started
Batch of 97 requests no. 2/2 ended
In [75]:
pd.set_option('display.max_columns', None)
response4df
Out[75]:
AverageSoFar AverageType BarrierLevel BarrierType BreakEvenDeltaAmountInDealCcy BreakEvenDeltaAmountInReportCcy BreakEvenPriceInDealCcy BreakEvenPriceInReportCcy CallPut CbbcOptionType CbbcType CharmAmountInDealCcy CharmAmountInReportCcy ColorAmountInDealCcy ColorAmountInReportCcy ConversionRatio DailyVolatility DailyVolatilityPercent DaysToExpiry DealCcy DeltaAmountInDealCcy DeltaAmountInReportCcy DeltaExposureInDealCcy DeltaExposureInReportCcy DeltaHedgePositionInDealCcy DeltaHedgePositionInReportCcy DeltaPercent DividendType DividendYieldPercent DvegaDtimeAmountInDealCcy DvegaDtimeAmountInReportCcy EndDate ErrorMessage ExerciseStyle FixingCalendar FixingDateArray FixingEndDate FixingFrequency FixingNumbers FixingStartDate ForecastDividendYieldPercent GammaAmountInDealCcy GammaAmountInReportCcy GammaPercent Gearing HedgeRatio InstrumentCode InstrumentDescription InstrumentTag Leverage LotSize LotsUnits MarketDataDate MarketValueInDealCcy MoneynessAmountInDealCcy MoneynessAmountInReportCcy OptionPrice OptionPriceSide OptionTimeStamp OptionType PremiumOverCashInDealCcy PremiumOverCashInReportCcy PremiumOverCashPercent PremiumPerAnnumInDealCcy PremiumPerAnnumInReportCcy PremiumPerAnnumPercent PremiumPercent PricingModelType PricingModelTypeList ResidualAmountInDealCcy ResidualAmountInReportCcy RhoAmountInDealCcy RhoAmountInReportCcy RhoPercent RiskFreeRatePercent SevenDaysThetaAmountInDealCcy SevenDaysThetaAmountInReportCcy SevenDaysThetaPercent SpeedAmountInDealCcy SpeedAmountInReportCcy Strike ThetaAmountInDealCcy ThetaAmountInReportCcy ThetaPercent TimeValueInDealCcy TimeValueInReportCcy TimeValuePerDay TimeValuePercent TotalMarketValueInDealCcy TotalMarketValueInDealCcy TotalMarketValueInReportCcy TotalMarketValueInReportCcy UltimaAmountInDealCcy UltimaAmountInReportCcy UnderlyingCcy UnderlyingPrice UnderlyingPriceSide UnderlyingRIC UnderlyingTimeStamp ValuationDate VannaAmountInDealCcy VannaAmountInReportCcy VegaAmountInDealCcy VegaAmountInReportCcy VegaPercent Volatility VolatilityPercent VolatilityType VolgaAmountInDealCcy VolgaAmountInReportCcy YearsToExpiry ZommaAmountInDealCcy ZommaAmountInReportCcy
0 <NA> NaN None 0.405318 0.405318 4269.0 4269.0 CALL <NA> -0.640398 -0.640398 -0.010471 -0.010471 0 1.137092 1.137092 29 EUR 6.25043 6.25043 -6.25043 -6.25043 -26321.686034 -26321.686034 0.625043 HistoricalYield 3.36275 -3063.740497 -3063.740497 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.017762 0.017762 0.001776 35.388067 -1.59989 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 22.119064 10 UNITS 2023-09-21 10:40:00 119.0 101.474217 101.474217 119.0 User Default Vanilla 57.82 57.82 1.373012 727.734483 727.734483 17.281011 2.825811 BlackScholes [BlackScholes, Binomial] 0 0 19.661623 19.661623 1.966162 3.955 -100.137886 -100.137886 -10.013789 -0.000003 -0.000003 4150 -14.305412 -14.305412 -1.430541 57.82 57.82 0.000473 1.373012 1190 1190 1190 1190 -3536.388733 -3536.388733 EUR 4211.18 User .STOXX50E Default 2023-09-21 10:40:00 -0.570353 -0.570353 44.483776 44.483776 4.448378 18.050781 18.050781 Calculated 217.060823 217.060823 0.079452 -0.008973 -0.008973
1 <NA> NaN None 0.408508 0.408508 4269.0 4269.0 CALL <NA> -0.693442 -0.693442 -0.010495 -0.010495 0 1.107898 1.107898 29 EUR 6.337198 6.337198 -6.337198 -6.337198 -26707.86898 -26707.86898 0.63372 HistoricalYield 3.36275 -3083.046904 -3083.046904 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.018078 0.018078 0.001808 35.41563 -1.577984 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 22.443587 10 UNITS 2023-09-21 10:50:00 119.0 101.553253 101.553253 119.0 User Default Vanilla 54.54 54.54 1.294116 686.451724 686.451724 16.288011 2.823612 BlackScholes [BlackScholes, Binomial] 0 0 19.958896 19.958896 1.99589 3.955 -96.997814 -96.997814 -9.699781 -0.000003 -0.000003 4150 -13.856831 -13.856831 -1.385683 54.54 54.54 0.000446 1.294116 1190 1190 1190 1190 -4287.69038 -4287.69038 EUR 4214.46 User .STOXX50E Default 2023-09-21 10:50:00 -0.632799 -0.632799 44.170843 44.170843 4.417084 17.587328 17.587328 Calculated 258.185203 258.185203 0.079452 -0.009223 -0.009223
2 <NA> NaN None 0.408127 0.408127 4270.8 4270.8 CALL <NA> -0.684186 -0.684186 -0.010336 -0.010336 0 1.129721 1.129721 29 EUR 6.323656 6.323656 -6.323656 -6.323656 -26653.579203 -26653.579203 0.632366 HistoricalYield 3.36275 -3080.599046 -3080.599046 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.017752 0.017752 0.001775 34.891556 -1.581364 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 22.064221 10 UNITS 2023-09-21 11:00:00 120.8 101.563855 101.563855 120.8 User Default Vanilla 55.9 55.9 1.326247 703.568966 703.568966 16.692424 2.866023 BlackScholes [BlackScholes, Binomial] 0 0 19.897513 19.897513 1.989751 3.955 -98.998021 -98.998021 -9.899802 -0.000003 -0.000003 4150 -14.142574 -14.142574 -1.414257 55.9 55.9 0.000457 1.326247 1208 1208 1208 1208 -4028.476227 -4028.476227 EUR 4214.9 User .STOXX50E Default 2023-09-21 11:00:00 -0.611776 -0.611776 44.225089 44.225089 4.422509 17.933765 17.933765 Calculated 246.998342 246.998342 0.079452 -0.008907 -0.008907
3 <NA> NaN None 0.40833 0.40833 4270.8 4270.8 CALL <NA> -0.687732 -0.687732 -0.010339 -0.010339 0 1.127968 1.127968 29 EUR 6.329225 6.329225 -6.329225 -6.329225 -26678.378902 -26678.378902 0.632922 HistoricalYield 3.36275 -3082.224817 -3082.224817 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.017771 0.017771 0.001777 34.893295 -1.579972 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 22.084751 10 UNITS 2023-09-21 11:10:00 120.8 101.568916 101.568916 120.8 User Default Vanilla 55.69 55.69 1.321199 700.925862 700.925862 16.628887 2.86588 BlackScholes [BlackScholes, Binomial] 0 0 19.91206 19.91206 1.991206 3.955 -98.816817 -98.816817 -9.881682 -0.000003 -0.000003 4150 -14.116688 -14.116688 -1.411669 55.69 55.69 0.000456 1.321199 1208 1208 1208 1208 -4076.084417 -4076.084417 EUR 4215.11 User .STOXX50E Default 2023-09-21 11:10:00 -0.615697 -0.615697 44.199427 44.199427 4.419943 17.905934 17.905934 Calculated 249.640264 249.640264 0.079452 -0.008921 -0.008921
4 <NA> NaN None 0.407163 0.407163 4270.8 4270.8 CALL <NA> -0.668511 -0.668511 -0.010336 -0.010336 0 1.138982 1.138982 29 EUR 6.297355 6.297355 -6.297355 -6.297355 -26536.423169 -26536.423169 0.629735 HistoricalYield 3.36275 -3075.55517 -3075.55517 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.017658 0.017658 0.001766 34.883278 -1.587968 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 21.967238 10 UNITS 2023-09-21 11:20:00 120.8 101.539759 101.539759 120.8 User Default Vanilla 56.9 56.9 1.350293 716.155172 716.155172 16.995068 2.866703 BlackScholes [BlackScholes, Binomial] 0 0 19.796264 19.796264 1.979626 3.955 -100.01756 -100.01756 -10.001756 -0.000003 -0.000003 4150 -14.288223 -14.288223 -1.428822 56.9 56.9 0.000466 1.350293 1208 1208 1208 1208 -3799.060357 -3799.060357 EUR 4213.9 User .STOXX50E Default 2023-09-21 11:20:00 -0.592847 -0.592847 44.30898 44.30898 4.430898 18.080781 18.080781 Calculated 234.349607 234.349607 0.079452 -0.008832 -0.008832
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
192 <NA> NaN None 0.360312 0.360312 4219.4 4219.4 CALL <NA> 0.310198 0.310198 -0.019318 -0.019318 0 1.015855 1.015855 23 EUR 5.218328 5.218328 -5.218328 -5.218328 -21637.639606 -21637.639606 0.521833 ImpliedYield 0.276 -3292.569362 -3292.569362 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.023887 0.023887 0.002389 59.747406 -1.916323 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 31.178155 10 UNITS 2023-09-27 07:20:00 69.4 99.91494 99.91494 69.4 User Default Vanilla 72.93 72.93 1.758845 1157.367391 1157.367391 27.912113 1.673713 BlackScholes [BlackScholes, Binomial] 0 0 13.022035 13.022035 1.302203 3.977 -116.931367 -116.931367 -11.693137 -0.000001 -0.000001 4150 -16.704481 -16.704481 -1.670448 69.4 69.4 0.000728 1.673713 694 694 694 694 -64.161454 -64.161454 EUR 4146.47 User .STOXX50E Default 2023-09-27 07:20:00 -0.036472 -0.036472 41.17855 41.17855 4.117855 16.1262 16.1262 Calculated 2.07321 2.07321 0.063014 -0.0148 -0.0148
193 <NA> NaN None 0.35187 0.35187 4219.4 4219.4 CALL <NA> 0.449125 0.449125 -0.018156 -0.018156 0 1.080531 1.080531 23 EUR 5.029848 5.029848 -5.029848 -5.029848 -20814.718503 -20814.718503 0.502985 ImpliedYield 0.276 -3305.541329 -3305.541329 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.022538 0.022538 0.002254 59.628818 -1.988132 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 29.99239 10 UNITS 2023-09-27 07:30:00 69.4 99.716627 99.716627 69.4 User Default Vanilla 81.16 81.16 1.96122 1287.973913 1287.973913 31.123712 1.677041 BlackScholes [BlackScholes, Binomial] 0 0 12.506542 12.506542 1.250654 3.977 -122.841327 -122.841327 -12.284133 -0.000001 -0.000001 4150 -17.548761 -17.548761 -1.754876 69.4 69.4 0.000729 1.677041 694 694 694 694 -14.250856 -14.250856 EUR 4138.24 User .STOXX50E Default 2023-09-27 07:30:00 0.081541 0.081541 41.151461 41.151461 4.115146 17.1529 17.1529 Calculated -0.647628 -0.647628 0.063014 -0.013143 -0.013143
194 <NA> NaN None 0.355624 0.355624 4219.4 4219.4 CALL <NA> 0.387733 0.387733 -0.018685 -0.018685 0 1.051534 1.051534 23 EUR 5.11301 5.11301 -5.11301 -5.11301 -21178.086348 -21178.086348 0.511301 ImpliedYield 0.276 -3299.910371 -3299.910371 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.023134 0.023134 0.002313 59.682997 -1.955795 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 30.515975 10 UNITS 2023-09-27 07:40:00 69.4 99.807229 99.807229 69.4 User Default Vanilla 77.4 77.4 1.868662 1228.304348 1228.304348 29.654861 1.675519 BlackScholes [BlackScholes, Binomial] 0 0 12.728506 12.728506 1.272851 3.977 -120.244062 -120.244062 -12.024406 -0.000001 -0.000001 4150 -17.177723 -17.177723 -1.717772 69.4 69.4 0.000728 1.675519 694 694 694 694 -9.052177 -9.052177 EUR 4142.0 User .STOXX50E Default 2023-09-27 07:40:00 0.031194 0.031194 41.166987 41.166987 4.116699 16.69259 16.69259 Calculated -0.919557 -0.919557 0.063014 -0.013864 -0.013864
195 <NA> NaN None 0.360421 0.360421 4219.4 4219.4 CALL <NA> 0.308338 0.308338 -0.019351 -0.019351 0 1.015509 1.015509 23 EUR 5.2208 5.2208 -5.2208 -5.2208 -21648.465853 -21648.465853 0.52208 ImpliedYield 0.276 -3293.980228 -3293.980228 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.023904 0.023904 0.00239 59.748991 -1.915415 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 31.193755 10 UNITS 2023-09-27 07:50:00 69.4 99.91759 99.91759 69.4 User Default Vanilla 72.82 72.82 1.756146 1155.621739 1155.621739 27.869274 1.673668 BlackScholes [BlackScholes, Binomial] 0 0 13.016806 13.016806 1.301681 3.977 -116.95026 -116.95026 -11.695026 -0.000001 -0.000001 4150 -16.70718 -16.70718 -1.670718 69.4 69.4 0.000728 1.673668 694 694 694 694 -66.29292 -66.29292 EUR 4146.58 User .STOXX50E Default 2023-09-27 07:50:00 -0.038096 -0.038096 41.159331 41.159331 4.115933 16.120708 16.120708 Calculated 2.189009 2.189009 0.063014 -0.014816 -0.014816
196 <NA> NaN None 0.366663 0.366663 4227.6 4227.6 CALL <NA> 0.188226 0.188226 -0.017922 -0.017922 0 1.092682 1.092682 23 EUR 5.349917 5.349917 -5.349917 -5.349917 -22215.958173 -22215.958173 0.534992 ImpliedYield 0.276 -3292.071586 -3292.071586 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.022136 0.022136 0.002214 53.512629 -1.869188 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 28.628812 10 UNITS 2023-09-27 08:00:00 77.6 100.062169 100.062169 77.6 User Default Vanilla 75.02 75.02 1.806588 1190.534783 1190.534783 28.669762 1.868718 BlackScholes [BlackScholes, Binomial] 0 0 13.314312 13.314312 1.331431 3.977 -124.986257 -124.986257 -12.498626 -0.000002 -0.000002 4150 -17.85518 -17.85518 -1.785518 75.02 75.02 0.000785 1.806588 776 776 776 776 -187.151273 -187.151273 EUR 4152.58 User .STOXX50E Default 2023-09-27 08:00:00 -0.102687 -0.102687 41.116616 41.116616 4.111662 17.345784 17.345784 Calculated 9.356929 9.356929 0.063014 -0.012711 -0.012711

197 rows × 113 columns

In [76]:
IPADf = response4df.copy()  # IPA here stands for the service we used to get all the calculated valuse, Instrument Pricint Analitycs.
IPADf.index = df_th.index
IPADf.columns.name = df_th.columns.name
IPADf.rename(inplace=True, columns={
    "Volatility": 'ImpliedVolatility',
    "MarketValueInDealCcy": f"OptnMrktValueInDealCcy-{__optn_mrkt_price_gmt.columns[0]}"})
IPADf
Out[76]:
STXE41500J3.EX AverageSoFar AverageType BarrierLevel BarrierType BreakEvenDeltaAmountInDealCcy BreakEvenDeltaAmountInReportCcy BreakEvenPriceInDealCcy BreakEvenPriceInReportCcy CallPut CbbcOptionType CbbcType CharmAmountInDealCcy CharmAmountInReportCcy ColorAmountInDealCcy ColorAmountInReportCcy ConversionRatio DailyVolatility DailyVolatilityPercent DaysToExpiry DealCcy DeltaAmountInDealCcy DeltaAmountInReportCcy DeltaExposureInDealCcy DeltaExposureInReportCcy DeltaHedgePositionInDealCcy DeltaHedgePositionInReportCcy DeltaPercent DividendType DividendYieldPercent DvegaDtimeAmountInDealCcy DvegaDtimeAmountInReportCcy EndDate ErrorMessage ExerciseStyle FixingCalendar FixingDateArray FixingEndDate FixingFrequency FixingNumbers FixingStartDate ForecastDividendYieldPercent GammaAmountInDealCcy GammaAmountInReportCcy GammaPercent Gearing HedgeRatio InstrumentCode InstrumentDescription InstrumentTag Leverage LotSize LotsUnits MarketDataDate OptnMrktValueInDealCcy-TRDPRC_1 MoneynessAmountInDealCcy MoneynessAmountInReportCcy OptionPrice OptionPriceSide OptionTimeStamp OptionType PremiumOverCashInDealCcy PremiumOverCashInReportCcy PremiumOverCashPercent PremiumPerAnnumInDealCcy PremiumPerAnnumInReportCcy PremiumPerAnnumPercent PremiumPercent PricingModelType PricingModelTypeList ResidualAmountInDealCcy ResidualAmountInReportCcy RhoAmountInDealCcy RhoAmountInReportCcy RhoPercent RiskFreeRatePercent SevenDaysThetaAmountInDealCcy SevenDaysThetaAmountInReportCcy SevenDaysThetaPercent SpeedAmountInDealCcy SpeedAmountInReportCcy Strike ThetaAmountInDealCcy ThetaAmountInReportCcy ThetaPercent TimeValueInDealCcy TimeValueInReportCcy TimeValuePerDay TimeValuePercent TotalMarketValueInDealCcy TotalMarketValueInDealCcy TotalMarketValueInReportCcy TotalMarketValueInReportCcy UltimaAmountInDealCcy UltimaAmountInReportCcy UnderlyingCcy UnderlyingPrice UnderlyingPriceSide UnderlyingRIC UnderlyingTimeStamp ValuationDate VannaAmountInDealCcy VannaAmountInReportCcy VegaAmountInDealCcy VegaAmountInReportCcy VegaPercent ImpliedVolatility VolatilityPercent VolatilityType VolgaAmountInDealCcy VolgaAmountInReportCcy YearsToExpiry ZommaAmountInDealCcy ZommaAmountInReportCcy
gmt cet
2023-09-21 10:40:00+00:00 2023-09-21 12:40:00+02:00 <NA> NaN None 0.405318 0.405318 4269.0 4269.0 CALL <NA> -0.640398 -0.640398 -0.010471 -0.010471 0 1.137092 1.137092 29 EUR 6.25043 6.25043 -6.25043 -6.25043 -26321.686034 -26321.686034 0.625043 HistoricalYield 3.36275 -3063.740497 -3063.740497 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.017762 0.017762 0.001776 35.388067 -1.59989 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 22.119064 10 UNITS 2023-09-21 10:40:00 119.0 101.474217 101.474217 119.0 User Default Vanilla 57.82 57.82 1.373012 727.734483 727.734483 17.281011 2.825811 BlackScholes [BlackScholes, Binomial] 0 0 19.661623 19.661623 1.966162 3.955 -100.137886 -100.137886 -10.013789 -0.000003 -0.000003 4150 -14.305412 -14.305412 -1.430541 57.82 57.82 0.000473 1.373012 1190 1190 1190 1190 -3536.388733 -3536.388733 EUR 4211.18 User .STOXX50E Default 2023-09-21 10:40:00 -0.570353 -0.570353 44.483776 44.483776 4.448378 18.050781 18.050781 Calculated 217.060823 217.060823 0.079452 -0.008973 -0.008973
2023-09-21 10:50:00+00:00 2023-09-21 12:50:00+02:00 <NA> NaN None 0.408508 0.408508 4269.0 4269.0 CALL <NA> -0.693442 -0.693442 -0.010495 -0.010495 0 1.107898 1.107898 29 EUR 6.337198 6.337198 -6.337198 -6.337198 -26707.86898 -26707.86898 0.63372 HistoricalYield 3.36275 -3083.046904 -3083.046904 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.018078 0.018078 0.001808 35.41563 -1.577984 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 22.443587 10 UNITS 2023-09-21 10:50:00 119.0 101.553253 101.553253 119.0 User Default Vanilla 54.54 54.54 1.294116 686.451724 686.451724 16.288011 2.823612 BlackScholes [BlackScholes, Binomial] 0 0 19.958896 19.958896 1.99589 3.955 -96.997814 -96.997814 -9.699781 -0.000003 -0.000003 4150 -13.856831 -13.856831 -1.385683 54.54 54.54 0.000446 1.294116 1190 1190 1190 1190 -4287.69038 -4287.69038 EUR 4214.46 User .STOXX50E Default 2023-09-21 10:50:00 -0.632799 -0.632799 44.170843 44.170843 4.417084 17.587328 17.587328 Calculated 258.185203 258.185203 0.079452 -0.009223 -0.009223
2023-09-21 11:00:00+00:00 2023-09-21 13:00:00+02:00 <NA> NaN None 0.408127 0.408127 4270.8 4270.8 CALL <NA> -0.684186 -0.684186 -0.010336 -0.010336 0 1.129721 1.129721 29 EUR 6.323656 6.323656 -6.323656 -6.323656 -26653.579203 -26653.579203 0.632366 HistoricalYield 3.36275 -3080.599046 -3080.599046 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.017752 0.017752 0.001775 34.891556 -1.581364 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 22.064221 10 UNITS 2023-09-21 11:00:00 120.8 101.563855 101.563855 120.8 User Default Vanilla 55.9 55.9 1.326247 703.568966 703.568966 16.692424 2.866023 BlackScholes [BlackScholes, Binomial] 0 0 19.897513 19.897513 1.989751 3.955 -98.998021 -98.998021 -9.899802 -0.000003 -0.000003 4150 -14.142574 -14.142574 -1.414257 55.9 55.9 0.000457 1.326247 1208 1208 1208 1208 -4028.476227 -4028.476227 EUR 4214.9 User .STOXX50E Default 2023-09-21 11:00:00 -0.611776 -0.611776 44.225089 44.225089 4.422509 17.933765 17.933765 Calculated 246.998342 246.998342 0.079452 -0.008907 -0.008907
2023-09-21 11:10:00+00:00 2023-09-21 13:10:00+02:00 <NA> NaN None 0.40833 0.40833 4270.8 4270.8 CALL <NA> -0.687732 -0.687732 -0.010339 -0.010339 0 1.127968 1.127968 29 EUR 6.329225 6.329225 -6.329225 -6.329225 -26678.378902 -26678.378902 0.632922 HistoricalYield 3.36275 -3082.224817 -3082.224817 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.017771 0.017771 0.001777 34.893295 -1.579972 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 22.084751 10 UNITS 2023-09-21 11:10:00 120.8 101.568916 101.568916 120.8 User Default Vanilla 55.69 55.69 1.321199 700.925862 700.925862 16.628887 2.86588 BlackScholes [BlackScholes, Binomial] 0 0 19.91206 19.91206 1.991206 3.955 -98.816817 -98.816817 -9.881682 -0.000003 -0.000003 4150 -14.116688 -14.116688 -1.411669 55.69 55.69 0.000456 1.321199 1208 1208 1208 1208 -4076.084417 -4076.084417 EUR 4215.11 User .STOXX50E Default 2023-09-21 11:10:00 -0.615697 -0.615697 44.199427 44.199427 4.419943 17.905934 17.905934 Calculated 249.640264 249.640264 0.079452 -0.008921 -0.008921
2023-09-21 11:20:00+00:00 2023-09-21 13:20:00+02:00 <NA> NaN None 0.407163 0.407163 4270.8 4270.8 CALL <NA> -0.668511 -0.668511 -0.010336 -0.010336 0 1.138982 1.138982 29 EUR 6.297355 6.297355 -6.297355 -6.297355 -26536.423169 -26536.423169 0.629735 HistoricalYield 3.36275 -3075.55517 -3075.55517 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.017658 0.017658 0.001766 34.883278 -1.587968 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 21.967238 10 UNITS 2023-09-21 11:20:00 120.8 101.539759 101.539759 120.8 User Default Vanilla 56.9 56.9 1.350293 716.155172 716.155172 16.995068 2.866703 BlackScholes [BlackScholes, Binomial] 0 0 19.796264 19.796264 1.979626 3.955 -100.01756 -100.01756 -10.001756 -0.000003 -0.000003 4150 -14.288223 -14.288223 -1.428822 56.9 56.9 0.000466 1.350293 1208 1208 1208 1208 -3799.060357 -3799.060357 EUR 4213.9 User .STOXX50E Default 2023-09-21 11:20:00 -0.592847 -0.592847 44.30898 44.30898 4.430898 18.080781 18.080781 Calculated 234.349607 234.349607 0.079452 -0.008832 -0.008832
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
2023-09-27 07:20:00+00:00 2023-09-27 09:20:00+02:00 <NA> NaN None 0.360312 0.360312 4219.4 4219.4 CALL <NA> 0.310198 0.310198 -0.019318 -0.019318 0 1.015855 1.015855 23 EUR 5.218328 5.218328 -5.218328 -5.218328 -21637.639606 -21637.639606 0.521833 ImpliedYield 0.276 -3292.569362 -3292.569362 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.023887 0.023887 0.002389 59.747406 -1.916323 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 31.178155 10 UNITS 2023-09-27 07:20:00 69.4 99.91494 99.91494 69.4 User Default Vanilla 72.93 72.93 1.758845 1157.367391 1157.367391 27.912113 1.673713 BlackScholes [BlackScholes, Binomial] 0 0 13.022035 13.022035 1.302203 3.977 -116.931367 -116.931367 -11.693137 -0.000001 -0.000001 4150 -16.704481 -16.704481 -1.670448 69.4 69.4 0.000728 1.673713 694 694 694 694 -64.161454 -64.161454 EUR 4146.47 User .STOXX50E Default 2023-09-27 07:20:00 -0.036472 -0.036472 41.17855 41.17855 4.117855 16.1262 16.1262 Calculated 2.07321 2.07321 0.063014 -0.0148 -0.0148
2023-09-27 07:30:00+00:00 2023-09-27 09:30:00+02:00 <NA> NaN None 0.35187 0.35187 4219.4 4219.4 CALL <NA> 0.449125 0.449125 -0.018156 -0.018156 0 1.080531 1.080531 23 EUR 5.029848 5.029848 -5.029848 -5.029848 -20814.718503 -20814.718503 0.502985 ImpliedYield 0.276 -3305.541329 -3305.541329 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.022538 0.022538 0.002254 59.628818 -1.988132 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 29.99239 10 UNITS 2023-09-27 07:30:00 69.4 99.716627 99.716627 69.4 User Default Vanilla 81.16 81.16 1.96122 1287.973913 1287.973913 31.123712 1.677041 BlackScholes [BlackScholes, Binomial] 0 0 12.506542 12.506542 1.250654 3.977 -122.841327 -122.841327 -12.284133 -0.000001 -0.000001 4150 -17.548761 -17.548761 -1.754876 69.4 69.4 0.000729 1.677041 694 694 694 694 -14.250856 -14.250856 EUR 4138.24 User .STOXX50E Default 2023-09-27 07:30:00 0.081541 0.081541 41.151461 41.151461 4.115146 17.1529 17.1529 Calculated -0.647628 -0.647628 0.063014 -0.013143 -0.013143
2023-09-27 07:40:00+00:00 2023-09-27 09:40:00+02:00 <NA> NaN None 0.355624 0.355624 4219.4 4219.4 CALL <NA> 0.387733 0.387733 -0.018685 -0.018685 0 1.051534 1.051534 23 EUR 5.11301 5.11301 -5.11301 -5.11301 -21178.086348 -21178.086348 0.511301 ImpliedYield 0.276 -3299.910371 -3299.910371 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.023134 0.023134 0.002313 59.682997 -1.955795 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 30.515975 10 UNITS 2023-09-27 07:40:00 69.4 99.807229 99.807229 69.4 User Default Vanilla 77.4 77.4 1.868662 1228.304348 1228.304348 29.654861 1.675519 BlackScholes [BlackScholes, Binomial] 0 0 12.728506 12.728506 1.272851 3.977 -120.244062 -120.244062 -12.024406 -0.000001 -0.000001 4150 -17.177723 -17.177723 -1.717772 69.4 69.4 0.000728 1.675519 694 694 694 694 -9.052177 -9.052177 EUR 4142.0 User .STOXX50E Default 2023-09-27 07:40:00 0.031194 0.031194 41.166987 41.166987 4.116699 16.69259 16.69259 Calculated -0.919557 -0.919557 0.063014 -0.013864 -0.013864
2023-09-27 07:50:00+00:00 2023-09-27 09:50:00+02:00 <NA> NaN None 0.360421 0.360421 4219.4 4219.4 CALL <NA> 0.308338 0.308338 -0.019351 -0.019351 0 1.015509 1.015509 23 EUR 5.2208 5.2208 -5.2208 -5.2208 -21648.465853 -21648.465853 0.52208 ImpliedYield 0.276 -3293.980228 -3293.980228 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.023904 0.023904 0.00239 59.748991 -1.915415 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 31.193755 10 UNITS 2023-09-27 07:50:00 69.4 99.91759 99.91759 69.4 User Default Vanilla 72.82 72.82 1.756146 1155.621739 1155.621739 27.869274 1.673668 BlackScholes [BlackScholes, Binomial] 0 0 13.016806 13.016806 1.301681 3.977 -116.95026 -116.95026 -11.695026 -0.000001 -0.000001 4150 -16.70718 -16.70718 -1.670718 69.4 69.4 0.000728 1.673668 694 694 694 694 -66.29292 -66.29292 EUR 4146.58 User .STOXX50E Default 2023-09-27 07:50:00 -0.038096 -0.038096 41.159331 41.159331 4.115933 16.120708 16.120708 Calculated 2.189009 2.189009 0.063014 -0.014816 -0.014816
2023-09-27 08:00:00+00:00 2023-09-27 10:00:00+02:00 <NA> NaN None 0.366663 0.366663 4227.6 4227.6 CALL <NA> 0.188226 0.188226 -0.017922 -0.017922 0 1.092682 1.092682 23 EUR 5.349917 5.349917 -5.349917 -5.349917 -22215.958173 -22215.958173 0.534992 ImpliedYield 0.276 -3292.071586 -3292.071586 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.022136 0.022136 0.002214 53.512629 -1.869188 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 28.628812 10 UNITS 2023-09-27 08:00:00 77.6 100.062169 100.062169 77.6 User Default Vanilla 75.02 75.02 1.806588 1190.534783 1190.534783 28.669762 1.868718 BlackScholes [BlackScholes, Binomial] 0 0 13.314312 13.314312 1.331431 3.977 -124.986257 -124.986257 -12.498626 -0.000002 -0.000002 4150 -17.85518 -17.85518 -1.785518 75.02 75.02 0.000785 1.806588 776 776 776 776 -187.151273 -187.151273 EUR 4152.58 User .STOXX50E Default 2023-09-27 08:00:00 -0.102687 -0.102687 41.116616 41.116616 4.111662 17.345784 17.345784 Calculated 9.356929 9.356929 0.063014 -0.012711 -0.012711

197 rows × 113 columns

At Option Trade Only¶

Delivery Layer¶

In [77]:
response5df = Fin_Contracts_Chunk_Calls_Delivery_Lay(
    req_univ=[{
        "instrumentType": "Option",
        "instrumentDefinition": {
            "buySell": "Buy",
            "underlyingType": "Eti",
            "instrumentCode": instrument,
            "strike": str(strikePrice)},
            "pricingParameters": {
                "valuationDate": str(
                    ATdf_th.index[i][0]).replace(" ", "T").split("+", 1)[0] + "Z", # optional, date-time # The original `dfLocalTimeZone` index provides dates in the form of '2023-06-15 15:40:00+01:00', so here we (i) change spaces with T, (ii) keep only the part of the str before the "+" and (iii) add "Z" at the end.
                "marketValueInDealCcy": str(ATdf_th.iloc[:,0][i]),
                "riskFreeRatePercent": str(ATdf_th['EurRfRate'][i]),
                "underlyingPrice": str(
                    ATdf_th['underlying ' + underlying + ' TRDPRC_1'][i]),
                "pricingModelType": "BlackScholes",
                "dividendType": "ImpliedYield",
                "volatilityType": "Implied",
                "underlyingTimeStamp": "Default",
                "reportCcy": "EUR"}}
        for i in range(len(ATdf_th))],
    request_fields=request_fields)
Batch of 100 requests no. 1/1 started
Batch of 100 requests no. 1/1 ended
In [78]:
response5df.tail(3)
Out[78]:
AverageSoFar AverageType BarrierLevel BarrierType BreakEvenDeltaAmountInDealCcy BreakEvenDeltaAmountInReportCcy BreakEvenPriceInDealCcy BreakEvenPriceInReportCcy CallPut CbbcOptionType CbbcType CharmAmountInDealCcy CharmAmountInReportCcy ColorAmountInDealCcy ColorAmountInReportCcy ConversionRatio DailyVolatility DailyVolatilityPercent DaysToExpiry DealCcy DeltaAmountInDealCcy DeltaAmountInReportCcy DeltaExposureInDealCcy DeltaExposureInReportCcy DeltaHedgePositionInDealCcy DeltaHedgePositionInReportCcy DeltaPercent DividendType DividendYieldPercent DvegaDtimeAmountInDealCcy DvegaDtimeAmountInReportCcy EndDate ErrorMessage ExerciseStyle FixingCalendar FixingDateArray FixingEndDate FixingFrequency FixingNumbers FixingStartDate ForecastDividendYieldPercent GammaAmountInDealCcy GammaAmountInReportCcy GammaPercent Gearing HedgeRatio InstrumentCode InstrumentDescription InstrumentTag Leverage LotSize LotsUnits MarketDataDate MarketValueInDealCcy MoneynessAmountInDealCcy MoneynessAmountInReportCcy OptionPrice OptionPriceSide OptionTimeStamp OptionType PremiumOverCashInDealCcy PremiumOverCashInReportCcy PremiumOverCashPercent PremiumPerAnnumInDealCcy PremiumPerAnnumInReportCcy PremiumPerAnnumPercent PremiumPercent PricingModelType PricingModelTypeList ResidualAmountInDealCcy ResidualAmountInReportCcy RhoAmountInDealCcy RhoAmountInReportCcy RhoPercent RiskFreeRatePercent SevenDaysThetaAmountInDealCcy SevenDaysThetaAmountInReportCcy SevenDaysThetaPercent SpeedAmountInDealCcy SpeedAmountInReportCcy Strike ThetaAmountInDealCcy ThetaAmountInReportCcy ThetaPercent TimeValueInDealCcy TimeValueInReportCcy TimeValuePerDay TimeValuePercent TotalMarketValueInDealCcy TotalMarketValueInDealCcy TotalMarketValueInReportCcy TotalMarketValueInReportCcy UltimaAmountInDealCcy UltimaAmountInReportCcy UnderlyingCcy UnderlyingPrice UnderlyingPriceSide UnderlyingRIC UnderlyingTimeStamp ValuationDate VannaAmountInDealCcy VannaAmountInReportCcy VegaAmountInDealCcy VegaAmountInReportCcy VegaPercent Volatility VolatilityPercent VolatilityType VolgaAmountInDealCcy VolgaAmountInReportCcy YearsToExpiry ZommaAmountInDealCcy ZommaAmountInReportCcy
41 None NaN None 0.347016 0.347016 4218.6 4218.6 CALL None 0.528664 0.528664 -0.017678 -0.017678 0.0 1.105255 1.105255 23.0 EUR 4.925191 4.925191 -4.925191 -4.925191 -20357.489414 -20357.489414 0.492519 ImpliedYield 0.276 -3313.848835 -3313.848835 2023-10-20T00:00:00Z EURO None None None None 0.0 None None 0.022050 0.022050 0.002205 60.252770 -2.030378 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX None 29.675641 10.0 UNITS 2023-09-27T07:10:00Z 68.6 99.598554 99.598554 68.6 User Default Vanilla 85.26 85.26 2.062739 1353.039130 1353.039130 32.734765 1.659675 BlackScholes [BlackScholes, Binomial] 0.0 0.0 12.234798 12.234798 1.223480 3.977 -124.839444 -124.839444 -12.483944 -3.074284e-07 -3.074284e-07 4150.0 -17.834206 -17.834206 -1.783421 68.60 68.60 0.000722 1.659675 686.0 686.0 686.0 686.0 -71.824334 -71.824334 EUR 4133.34 User .STOXX50E Default 2023-09-27T07:10:00Z 0.141601 0.141601 41.109461 41.109461 4.110946 17.545386 17.545386 Calculated 2.706310 2.706310 0.063014 -0.012553 -0.012553
42 None NaN None 0.360312 0.360312 4219.4 4219.4 CALL None 0.310198 0.310198 -0.019318 -0.019318 0.0 1.015855 1.015855 23.0 EUR 5.218328 5.218328 -5.218328 -5.218328 -21637.639606 -21637.639606 0.521833 ImpliedYield 0.276 -3292.569362 -3292.569362 2023-10-20T00:00:00Z EURO None None None None 0.0 None None 0.023887 0.023887 0.002389 59.747406 -1.916323 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX None 31.178155 10.0 UNITS 2023-09-27T07:20:00Z 69.4 99.914940 99.914940 69.4 User Default Vanilla 72.93 72.93 1.758845 1157.367391 1157.367391 27.912113 1.673713 BlackScholes [BlackScholes, Binomial] 0.0 0.0 13.022035 13.022035 1.302203 3.977 -116.931367 -116.931367 -11.693137 -1.363708e-06 -1.363708e-06 4150.0 -16.704481 -16.704481 -1.670448 69.40 69.40 0.000728 1.673713 694.0 694.0 694.0 694.0 -64.161454 -64.161454 EUR 4146.47 User .STOXX50E Default 2023-09-27T07:20:00Z -0.036472 -0.036472 41.178550 41.178550 4.117855 16.126200 16.126200 Calculated 2.073210 2.073210 0.063014 -0.014800 -0.014800
43 None NaN None 0.366663 0.366663 4227.6 4227.6 CALL None 0.188226 0.188226 -0.017922 -0.017922 0.0 1.092682 1.092682 23.0 EUR 5.349917 5.349917 -5.349917 -5.349917 -22215.958173 -22215.958173 0.534992 ImpliedYield 0.276 -3292.071586 -3292.071586 2023-10-20T00:00:00Z EURO None None None None 0.0 None None 0.022136 0.022136 0.002214 53.512629 -1.869188 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX None 28.628812 10.0 UNITS 2023-09-27T08:00:00Z 77.6 100.062169 100.062169 77.6 User Default Vanilla 75.02 75.02 1.806588 1190.534783 1190.534783 28.669762 1.868718 BlackScholes [BlackScholes, Binomial] 0.0 0.0 13.314312 13.314312 1.331431 3.977 -124.986257 -124.986257 -12.498626 -1.618946e-06 -1.618946e-06 4150.0 -17.855180 -17.855180 -1.785518 75.02 75.02 0.000785 1.806588 776.0 776.0 776.0 776.0 -187.151273 -187.151273 EUR 4152.58 User .STOXX50E Default 2023-09-27T08:00:00Z -0.102687 -0.102687 41.116616 41.116616 4.111662 17.345784 17.345784 Calculated 9.356929 9.356929 0.063014 -0.012711 -0.012711

Content Layer¶

In [79]:
response6df = Fin_Contracts_Chunk_Calls_Content_Lay(
    req_univ=[  # C here is for the fact that we're using the content layer
        option.Definition(
            underlying_type=option.UnderlyingType.ETI,
            buy_sell='Buy',
            instrument_code=instrument,
            strike=float(strikePrice),
            pricing_parameters=option.PricingParameters(
                valuation_date=str(
                    ATdf_th.index[i][0]).replace(" ", "T").split("+", 1)[0] + "Z",
                market_value_in_deal_ccy=float(ATdf_th.iloc[:,0][i]),
                risk_free_rate_percent=float(ATdf_th['EurRfRate'][i]),
                underlying_price=float(
                    ATdf_th['underlying ' + underlying + ' TRDPRC_1'][i]),
                pricing_model_type='BlackScholes',
                volatility_type='Implied',
                underlying_time_stamp='Default',
                report_ccy='EUR'))
        for i in range(len(ATdf_th))],
    request_fields=request_fields)
Batch of 44 requests no. 1/1 started
Batch of 44 requests no. 1/1 ended
In [80]:
ATIPADf = response6df.copy()  # IPA here stands for the service we used to get all the calculated valuse, Instrument Pricint Analitycs.
ATIPADf.index = ATdf_th.index
ATIPADf.columns.name = ATdf_th.columns.name
ATIPADf.rename(inplace=True, columns={
    "Volatility": 'ImpliedVolatility',
    "MarketValueInDealCcy": f"OptnMrktValueInDealCcy-{__optn_mrkt_price_gmt.columns[0]}"})
ATIPADf.head(2)
Out[80]:
STXE41500J3.EX AverageSoFar AverageType BarrierLevel BarrierType BreakEvenDeltaAmountInDealCcy BreakEvenDeltaAmountInReportCcy BreakEvenPriceInDealCcy BreakEvenPriceInReportCcy CallPut CbbcOptionType CbbcType CharmAmountInDealCcy CharmAmountInReportCcy ColorAmountInDealCcy ColorAmountInReportCcy ConversionRatio DailyVolatility DailyVolatilityPercent DaysToExpiry DealCcy DeltaAmountInDealCcy DeltaAmountInReportCcy DeltaExposureInDealCcy DeltaExposureInReportCcy DeltaHedgePositionInDealCcy DeltaHedgePositionInReportCcy DeltaPercent DividendType DividendYieldPercent DvegaDtimeAmountInDealCcy DvegaDtimeAmountInReportCcy EndDate ErrorMessage ExerciseStyle FixingCalendar FixingDateArray FixingEndDate FixingFrequency FixingNumbers FixingStartDate ForecastDividendYieldPercent GammaAmountInDealCcy GammaAmountInReportCcy GammaPercent Gearing HedgeRatio InstrumentCode InstrumentDescription InstrumentTag Leverage LotSize LotsUnits MarketDataDate OptnMrktValueInDealCcy-TRDPRC_1 MoneynessAmountInDealCcy MoneynessAmountInReportCcy OptionPrice OptionPriceSide OptionTimeStamp OptionType PremiumOverCashInDealCcy PremiumOverCashInReportCcy PremiumOverCashPercent PremiumPerAnnumInDealCcy PremiumPerAnnumInReportCcy PremiumPerAnnumPercent PremiumPercent PricingModelType PricingModelTypeList ResidualAmountInDealCcy ResidualAmountInReportCcy RhoAmountInDealCcy RhoAmountInReportCcy RhoPercent RiskFreeRatePercent SevenDaysThetaAmountInDealCcy SevenDaysThetaAmountInReportCcy SevenDaysThetaPercent SpeedAmountInDealCcy SpeedAmountInReportCcy Strike ThetaAmountInDealCcy ThetaAmountInReportCcy ThetaPercent TimeValueInDealCcy TimeValueInReportCcy TimeValuePerDay TimeValuePercent TotalMarketValueInDealCcy TotalMarketValueInDealCcy TotalMarketValueInReportCcy TotalMarketValueInReportCcy UltimaAmountInDealCcy UltimaAmountInReportCcy UnderlyingCcy UnderlyingPrice UnderlyingPriceSide UnderlyingRIC UnderlyingTimeStamp ValuationDate VannaAmountInDealCcy VannaAmountInReportCcy VegaAmountInDealCcy VegaAmountInReportCcy VegaPercent ImpliedVolatility VolatilityPercent VolatilityType VolgaAmountInDealCcy VolgaAmountInReportCcy YearsToExpiry ZommaAmountInDealCcy ZommaAmountInReportCcy
gmt cet
2023-09-21 10:40:00+00:00 2023-09-21 12:40:00+02:00 <NA> NaN None 0.405318 0.405318 4269.0 4269.0 CALL <NA> -0.640398 -0.640398 -0.010471 -0.010471 0 1.137092 1.137092 29 EUR 6.25043 6.25043 -6.25043 -6.25043 -26321.686034 -26321.686034 0.625043 HistoricalYield 3.36275 -3063.740497 -3063.740497 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.017762 0.017762 0.001776 35.388067 -1.59989 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 22.119064 10 UNITS 2023-09-21 10:40:00 119.0 101.474217 101.474217 119.0 User Default Vanilla 57.82 57.82 1.373012 727.734483 727.734483 17.281011 2.825811 BlackScholes [BlackScholes, Binomial] 0 0 19.661623 19.661623 1.966162 3.955 -100.137886 -100.137886 -10.013789 -0.000003 -0.000003 4150 -14.305412 -14.305412 -1.430541 57.82 57.82 0.000473 1.373012 1190 1190 1190 1190 -3536.388733 -3536.388733 EUR 4211.18 User .STOXX50E Default 2023-09-21 10:40:00 -0.570353 -0.570353 44.483776 44.483776 4.448378 18.050781 18.050781 Calculated 217.060823 217.060823 0.079452 -0.008973 -0.008973
2023-09-21 11:00:00+00:00 2023-09-21 13:00:00+02:00 <NA> NaN None 0.408127 0.408127 4270.8 4270.8 CALL <NA> -0.684186 -0.684186 -0.010336 -0.010336 0 1.129721 1.129721 29 EUR 6.323656 6.323656 -6.323656 -6.323656 -26653.579203 -26653.579203 0.632366 HistoricalYield 3.36275 -3080.599046 -3080.599046 2023-10-20 EURO <NA> <NA> NaT <NA> 0 NaT <NA> 0.017752 0.017752 0.001775 34.891556 -1.581364 STXE41500J3.EX IndexOption_EURO_STXE41500J3.EX <NA> 22.064221 10 UNITS 2023-09-21 11:00:00 120.8 101.563855 101.563855 120.8 User Default Vanilla 55.9 55.9 1.326247 703.568966 703.568966 16.692424 2.866023 BlackScholes [BlackScholes, Binomial] 0 0 19.897513 19.897513 1.989751 3.955 -98.998021 -98.998021 -9.899802 -0.000003 -0.000003 4150 -14.142574 -14.142574 -1.414257 55.9 55.9 0.000457 1.326247 1208 1208 1208 1208 -4028.476227 -4028.476227 EUR 4214.9 User .STOXX50E Default 2023-09-21 11:00:00 -0.611776 -0.611776 44.225089 44.225089 4.422509 17.933765 17.933765 Calculated 246.998342 246.998342 0.079452 -0.008907 -0.008907

Graphs¶

All Data¶

Overlay¶
In [81]:
def Get_ooth(
        df,
        tz=None,
        mrkt_exhng_open_time=mrkt_exhng_open_time,
        mrkt_exhng_close_time=mrkt_exhng_close_time):
    """Get_ooth(df, tz=None, mrkt_exhng_open_time=mrkt_exhng_close_time, mrkt_exhng_close_time=mrkt_exhng_close_time))

    df (pandas dataframe):
        Must have dates as `pd.Timestamp`s in its index.
    
    returns:
        List of list of two objects: the start second of each weekend day (saturnday 1st, sunday 2nd).
    """
    ooth = []
    for k in range(len(df)-1):
        if len(pd.bdate_range(start=df.index[k], end=df.index[k+1], freq="C", weekmask="Sat Sun")) > 0:
            for saturday in [j for j in pd.bdate_range(start=df.index[k], end=df.index[k+1], freq="C", weekmask="Sat")]:
                # Append with the the first second of that Saturday
                ooth.append([
                    pd.Timestamp(
                        year=saturday.year,
                        month=saturday.month,
                        day=saturday.day,
                        tzinfo=tz),
                    pd.Timestamp(
                        year=saturday.year,
                        month=saturday.month,
                        day=saturday.day,
                        hour=23,
                        minute=59,
                        second=59,
                        tzinfo=tz)])
            for sunday in [j for j in pd.bdate_range(start=df.index[k], end=df.index[k+1], freq="C", weekmask="Sun")]:
                ooth.append([
                    pd.Timestamp(
                        year=sunday.year,
                        month=sunday.month,
                        day=sunday.day,
                        tzinfo=tz),
                    pd.Timestamp(
                        year=sunday.year,
                        month=sunday.month,
                        day=sunday.day,
                        hour=23,
                        minute=59,
                        second=59,
                        tzinfo=tz)])
        else:
            for weekday in [j for j in pd.bdate_range(start=df.index[k], end=df.index[k+1], freq="C", weekmask="Mon Tue Wed Thu Fri")]:
                ooth.append([  # Begining of day till mrkt open.
                    pd.Timestamp(
                        year=weekday.year,
                        month=weekday.month,
                        day=weekday.day,
                        hour=0,
                        minute=0,
                        second=1,
                        tzinfo=tz),
                    pd.Timestamp(
                        year=weekday.year,
                        month=weekday.month,
                        day=weekday.day,
                        hour=int(mrkt_exhng_open_time.split(':')[0]),
                        minute=int(mrkt_exhng_open_time.split(':')[1]),
                        tzinfo=tz)])
                ooth.append([  # Mrkt close to end of day.
                    pd.Timestamp(
                        year=weekday.year,
                        month=weekday.month,
                        day=weekday.day,
                        hour=int(mrkt_exhng_close_time.split(':')[0]),
                        minute=int(mrkt_exhng_close_time.split(':')[1]),
                        tzinfo=tz),
                    pd.Timestamp(
                        year=weekday.year,
                        month=weekday.month,
                        day=weekday.day,
                        hour=23,
                        minute=59,
                        second=59,
                        tzinfo=tz)])
                
    ooth = pd.Series(ooth).drop_duplicates().tolist() # There are duplicates to remove.

    return ooth[1:-1] # We do not want to see the out of trading hours before the data starts and ends on the graph.
In [82]:
IPADfGraph = IPADf[[
    'ImpliedVolatility', f"OptnMrktValueInDealCcy-{__optn_mrkt_price_gmt.columns[0]}",
    'RiskFreeRatePercent', 'UnderlyingPrice', 'DeltaPercent',
    'GammaPercent', 'RhoPercent', 'ThetaPercent', 'VegaPercent']].droplevel('gmt')
In [83]:
display(closest_atm_optn)

fig = px.line(IPADfGraph)  # This is just to see the implied vol graph when that field is available
# fig.layout = dict(xaxis=dict(type="category"))

# Format Graph: https://plotly.com/python/tick-formatting/
fig.update_layout(
    title=instrument,
    template='plotly_dark')

# Make it so that only one line is shown by default: # https://stackoverflow.com/questions/73384807/plotly-express-plot-subset-of-dataframe-columns-by-default-and-the-rest-as-opt
fig.for_each_trace(
    lambda t: t.update(
        visible=True if t.name in IPADfGraph.columns[:1] else "legendonly"))

# Add shade in Non Trading Hours
shapes = [
    {'type': "rect", 'xref': 'x', 'yref': 'paper',
     'fillcolor': 'purple', 'opacity': 0.4,
     "line": {"color": "purple", "width": 0},
     'x0': i[0], 'y0': 0,
     'x1': i[1], 'y1': 1}
     for i in Get_ooth(df=IPADfGraph, tz=None)]

fig.update_layout(shapes=shapes,)


fig.show()
DocumentTitle RIC StrikePrice ExchangeCode ExpiryDate UnderlyingQuoteRIC InsertDateTime RetireDate
14 Eurex EURO STOXX 50 Monthly Index Option 4150 ... STXE41500J3.EX 4150 EUX 2023-10-20 [.STOXX50E] 2023-03-09 03:48:43 2023-10-24
Sack of 3 Graphs¶

This representation will allow us to see several graphs at different scales stacked above one another. This way, we can see if the change in Implied Volatility is caused by a movement in the underlying or the Option price itself:

In [84]:
display(closest_atm_optn)

fig = subplots.make_subplots(rows=3, cols=1)

fig.add_trace(go.Scatter(
    x=IPADfGraph.index, y=IPADfGraph.ImpliedVolatility,
    name='Op Imp Volatility'), row=1, col=1)
fig.add_trace(go.Scatter(
    y=IPADfGraph[f"OptnMrktValueInDealCcy-{__optn_mrkt_price_gmt.columns[0]}"],
    x=IPADfGraph.index,
    name=f"OptnMrktValueInDealCcy-{__optn_mrkt_price_gmt.columns[0]}"), row=2, col=1)
fig.add_trace(go.Scatter(
    x=IPADfGraph.index, y=IPADf.UnderlyingPrice,
    name=underlying + ' Undrlyg Pr'), row=3, col=1)


fig.update(layout_xaxis_rangeslider_visible=False)
fig.update_layout(title=IPADfGraph.columns.name)

fig.update_layout(
    template='plotly_dark',
    autosize=False,
    width=1300,
    height=500,
    shapes = [ # Adding purple area on out of trading hours
        {'type': "rect", 'xref': 'x', 'yref': 'paper',
        'fillcolor': 'purple', 'opacity': 0.2,
        "line": {"color": "purple", "width": 0},
        'x0': i[0], 'y0': 0,
        'x1': i[1], 'y1': 1}
        for i in Get_ooth(df=IPADfGraph, tz=None)])

fig.show()
DocumentTitle RIC StrikePrice ExchangeCode ExpiryDate UnderlyingQuoteRIC InsertDateTime RetireDate
14 Eurex EURO STOXX 50 Monthly Index Option 4150 ... STXE41500J3.EX 4150 EUX 2023-10-20 [.STOXX50E] 2023-03-09 03:48:43 2023-10-24
Simple Graph¶

Certain companies are slow to update libraries, dependencies or Python versions. They/You may thus not have access to plotly (the graph library we used above). Matplotlib is rather light and should work, even on machines with old setups:

In [85]:
display(closest_atm_optn)
IPADfSimpleGraph = pd.DataFrame(
    data=IPADfGraph.ImpliedVolatility.values,
    index=IPADfGraph.ImpliedVolatility.index)

fig, ax = plt.subplots(ncols=1)

ax.plot(IPADfSimpleGraph, '.-')
# ax.xaxis.set_major_formatter(ticker.FuncFormatter(format_date))
ax.set_title(
    f"{closest_atm_optn.RIC.values[0]} Implied Volatility At Trade Only")
fig.autofmt_xdate()

plt.show()
DocumentTitle RIC StrikePrice ExchangeCode ExpiryDate UnderlyingQuoteRIC InsertDateTime RetireDate
14 Eurex EURO STOXX 50 Monthly Index Option 4150 ... STXE41500J3.EX 4150 EUX 2023-10-20 [.STOXX50E] 2023-03-09 03:48:43 2023-10-24

At Trade Data¶

Note here that we are now looking only 'At Trade', i.e.: times when the option traded, not the underlying. There are therefore fewer datapoints.

Overlay¶
In [86]:
IPADfGraph = ATIPADf[[
    'ImpliedVolatility', f"OptnMrktValueInDealCcy-{__optn_mrkt_price_gmt.columns[0]}",
    'RiskFreeRatePercent', 'UnderlyingPrice', 'DeltaPercent',
    'GammaPercent', 'RhoPercent', 'ThetaPercent', 'VegaPercent']].droplevel('gmt')
In [87]:
display(closest_atm_optn)

fig = px.line(IPADfGraph)  # This is just to see the implied vol graph when that field is available
# fig.layout = dict(xaxis=dict(type="category"))

# Format Graph: https://plotly.com/python/tick-formatting/
fig.update_layout(
    title=instrument,
    template='plotly_dark')

# Make it so that only one line is shown by default: # https://stackoverflow.com/questions/73384807/plotly-express-plot-subset-of-dataframe-columns-by-default-and-the-rest-as-opt
fig.for_each_trace(
    lambda t: t.update(
        visible=True if t.name in IPADfGraph.columns[:1] else "legendonly"))

# Add shade in Non Trading Hours
shapes = [
    {'type': "rect", 'xref': 'x', 'yref': 'paper',
     'fillcolor': 'purple', 'opacity': 0.4,
     "line": {"color": "purple", "width": 0},
     'x0': i[0], 'y0': 0,
     'x1': i[1], 'y1': 1}
     for i in Get_ooth(df=IPADfGraph, tz=None)]

fig.update_layout(shapes=shapes,)


fig.show()
DocumentTitle RIC StrikePrice ExchangeCode ExpiryDate UnderlyingQuoteRIC InsertDateTime RetireDate
14 Eurex EURO STOXX 50 Monthly Index Option 4150 ... STXE41500J3.EX 4150 EUX 2023-10-20 [.STOXX50E] 2023-03-09 03:48:43 2023-10-24
Sack of 3 Graphs¶

This representation will allow us to see several graphs at different scales stacked above one another. This way, we can see if the change in Implied Volatility is caused by a movement in the underlying or the Option price itself:

In [88]:
display(closest_atm_optn)

fig = subplots.make_subplots(rows=3, cols=1)

fig.add_trace(go.Scatter(
    x=IPADfGraph.index, y=IPADfGraph.ImpliedVolatility,
    name='Op Imp Volatility'), row=1, col=1)
fig.add_trace(go.Scatter(
    y=IPADfGraph[f"OptnMrktValueInDealCcy-{__optn_mrkt_price_gmt.columns[0]}"],
    x=IPADfGraph.index,
    name=f"OptnMrktValueInDealCcy-{__optn_mrkt_price_gmt.columns[0]}"), row=2, col=1)
fig.add_trace(go.Scatter(
    x=IPADfGraph.index, y=IPADf.UnderlyingPrice,
    name=underlying + ' Undrlyg Pr'), row=3, col=1)


fig.update(layout_xaxis_rangeslider_visible=False)
fig.update_layout(title=IPADfGraph.columns.name)

fig.update_layout(
    template='plotly_dark',
    autosize=False,
    width=1300,
    height=500,
    shapes = [ # Adding purple area on out of trading hours
        {'type': "rect", 'xref': 'x', 'yref': 'paper',
        'fillcolor': 'purple', 'opacity': 0.2,
        "line": {"color": "purple", "width": 0},
        'x0': i[0], 'y0': 0,
        'x1': i[1], 'y1': 1}
        for i in Get_ooth(df=IPADfGraph, tz=None)])

fig.show()
DocumentTitle RIC StrikePrice ExchangeCode ExpiryDate UnderlyingQuoteRIC InsertDateTime RetireDate
14 Eurex EURO STOXX 50 Monthly Index Option 4150 ... STXE41500J3.EX 4150 EUX 2023-10-20 [.STOXX50E] 2023-03-09 03:48:43 2023-10-24
Simple Graph¶

Certain companies are slow to update libraries, dependencies or Python versions. They/You may thus not have access to plotly (the graph library we used above). Matplotlib is rather light and should work, even on machines with old setups:

In [89]:
display(closest_atm_optn)
IPADfSimpleGraph = pd.DataFrame(
    data=IPADfGraph.ImpliedVolatility.values,
    index=IPADfGraph.ImpliedVolatility.index)

fig, ax = plt.subplots(ncols=1)

ax.plot(IPADfSimpleGraph, '.-')
# ax.xaxis.set_major_formatter(ticker.FuncFormatter(format_date))
ax.set_title(
    f"{closest_atm_optn.RIC.values[0]} Implied Volatility At Trade Only")
fig.autofmt_xdate()

plt.show()
DocumentTitle RIC StrikePrice ExchangeCode ExpiryDate UnderlyingQuoteRIC InsertDateTime RetireDate
14 Eurex EURO STOXX 50 Monthly Index Option 4150 ... STXE41500J3.EX 4150 EUX 2023-10-20 [.STOXX50E] 2023-03-09 03:48:43 2023-10-24